2017-08-31 69 views
1

一直在努力学习如何实现服务,因为他们得到一个监听器触发。 Havebeendoingaseriouslotofreadingthelast几天来得到它的工作,但都被发现很难。因此,我认为我对事物顺序的理解可能有缺陷。ZF2 - 如何监听事件并因此触发服务?

使用情况下,我试图去工作如下

仅仅是一个地址实体之前(与学说,但这不是 重要)获取保存(刷新),服务必须被触发,以检查 如果地址坐标设置,如果没有,创建和 填补了新坐标实体,并将其链接到地址。该 坐标是从谷歌地图API地理编码得到。

下面会显示什么以及我如何理解事情,希望我自己清楚。据我所知,它会逐步完成显示附加代码,并告诉你什么工作,哪些工作不工作。

现在,我所有的我已经得到了最近几天的信息的理解是这样的:

器监听器必须与ZF2的的ServiceManager注册。监听器将某些条件“附加”到(共享)EventManager。 EventManager对象是唯一的,但SharedEventManager在应用程序中是“全局”的。

在地址模块的Module.php类我已经添加了以下功能:

/** 
* @param EventInterface $e 
*/ 
public function onBootstrap(EventInterface $e) 
{ 
    $eventManager = $e->getTarget()->getEventManager(); 
    $eventManager->attach(new AddressListener()); 
} 

这得到工程,AddressListener被触发。

的AddressListener如下:

use Address\Entity\Address; 
use Address\Service\GoogleCoordinatesService; 
use Zend\EventManager\EventManagerInterface; 
use Zend\EventManager\ListenerAggregateInterface; 
use Zend\Stdlib\CallbackHandler; 

class AddressListener implements ListenerAggregateInterface 
{ 
    /** 
    * @var CallbackHandler 
    */ 
    protected $listeners; 

    /** 
    * @param EventManagerInterface $events 
    */ 
    public function attach(EventManagerInterface $events) 
    { 
     $sharedEvents = $events->getSharedManager(); 

     // Not sure how and what order params should be. The ListenerAggregateInterface docblocks didn't help me a lot with that either, as did the official ZF2 docs. So, been trying a few things... 

     $this->listeners[] = $sharedEvents->attach(GoogleCoordinatesService::class, 'getCoordinates', [$this, 'addressCreated'], 100); 

     $this->listeners[] = $sharedEvents->attach(Address::class, 'entity.preFlush', [GoogleCoordinatesService::class, 'getCoordinates'], 100); 
    } 

    /** 
    * @param EventManagerInterface $events 
    */ 
    public function detach(EventManagerInterface $events) 
    { 
     foreach ($this->listeners as $index => $listener) { 
      if ($events->detach($listener)) { 
       unset($this->listeners[$index]); 
      } 
     } 
    } 

    public function addressCreated() 
    { 
     $foo = 'bar'; // This line is here to as debug break. Line is never used... 
    } 
} 

我期待一个监听器作为工作跳板点,事情被触发,基于function attach(...){}->attach()功能排序的。然而,这似乎并不奏效,因为没有任何东西被触发。不是addressCreated()功能,而不是getCoordinates功能在GoogleCoordinatesService

上面的代码应该触发GoogleCoordinatesService功能getCoordinates。尽管该服务有一些要求,例如存在Doctrine的EntityManager,它涉及的地址实体和配置。

为此,我创建了以下配置。

文件google.config.php(被加载时,经检查发现)

return [ 
    'google' => [ 
     'services' => [ 
      'maps' => [ 
       'services' => [ 
        'geocoding' => [ 
         'api_url' => 'https://maps.googleapis.com/maps/api/geocode/json?', 
         'api_key' => '', 
         'url_params' => [ 
          'required' => [ 
           'address', 
          ], 
          'optional' => [ 
           'key' 
          ], 
         ], 
        ], 
       ], 
      ], 
     ], 
    ], 
]; 

而在module.config.php我所注册的服务与厂

'service_manager' => [ 
    'factories' => [ 
     GoogleCoordinatesService::class => GoogleCoordinatesServiceFactory::class, 
    ], 
], 

工厂是非常标准的ZF2的东西,但画一个完整的图片,这里是GoogleCoordinatesServiceFactory.php类。 (删除评论/提示/等)

class GoogleCoordinatesServiceFactory implements FactoryInterface 
{ 
    public function createService(ServiceLocatorInterface $serviceLocator, $options = []) 
    { 
     $serviceManager = $serviceLocator->getServiceLocator(); 
     $entityManager = $serviceManager->get(EntityManager::class); 
     $config = $serviceManager->get('Config'); 

     if (isset($options) && isset($options['address'])) { 
      $address = $options['address']; 
     } else { 
      throw new InvalidArgumentException('Must provide an Address Entity.'); 
     } 

     return new GoogleCoordinatesService(
      $entityManager, 
      $config, 
      $address 
     ); 
    } 
} 

以下是GoogleCoordinatesService类。然而,在那里没有任何东西被触发执行。由于它甚至没有被调用,我敢肯定问题在于上面的代码,但无法找出原因。从我读过的和尝试过的,我期望班级本身应该被调用,通过工厂,并且应该触发功能getCoordinates

所以,这个班级。我已经删除了一大堆标准的getter/setter,注释,docblocks和typehints以缩短它。

class GoogleCoordinatesService implements EventManagerAwareInterface 
{ 
    protected $eventManager; 
    protected $entityManager; 
    protected $config; 
    protected $address; 

    /** 
    * GoogleCoordinatesServices constructor. 
    * @param EntityManager $entityManager 
    * @param Config|array $config 
    * @param Address $address 
    * @throws InvalidParamNameException 
    */ 
    public function __construct(EntityManager $entityManager, $config, Address $address) 
    {  
     $this->config = $config; 
     $this->address = $address; 
     $this->entityManager = $entityManager; 
    } 

    public function getCoordinates() 
    { 
     $url = $this->getConfig()['api_url'] . 'address=' . $this->urlFormatAddress($this->getAddress()); 

     $response = json_decode(file_get_contents($url), true); 

     if ($response['status'] == 'OK') { 
      $coordinates = new Coordinates(); 
      $coordinates 
       ->setLatitude($response['results'][0]['geometry']['location']['lat']) 
       ->setLongitude($response['results'][0]['geometry']['location']['lng']); 

      $this->getEntityManager()->persist($coordinates); 

      $this->getAddress()->setCoordinates($coordinates); 
      $this->getEntityManager()->persist($this->getAddress()); 

      $this->getEntityManager()->flush(); 

      $this->getEventManager()->trigger(
       'addressReceivedCoordinates', 
       null, 
       ['address' => $this->getAddress()] 
      ); 
     } else { 
      // TODO throw/set error/status 
     } 
    } 

    public function urlFormatAddress(Address $address) 
    { 
     $string = // format the address into a string 

     return urlencode($string); 
    } 

    public function getEventManager() 
    { 
     if ($this->eventManager === null) { 
      $this->setEventManager(new EventManager()); 
     } 

     return $this->eventManager; 
    } 

    public function setEventManager(EventManagerInterface $eventManager) 
    { 
     $eventManager->addIdentifiers([ 
      __CLASS__, 
      get_called_class() 
     ]); 

     $this->eventManager = $eventManager; 
     return $this; 
    } 

    // Getters/Setters for EntityManager, Config and Address 
} 

所以,这是当某个事件被触发时处理它的设置。现在它应该被触发。对于这个用例,我在我自己的AbstractActionController中设置了一个触发器(扩展了ZF2的AbstractActionController)。这样做:

if ($form->isValid()) { 
    $entity = $form->getObject(); 
    $this->getEntityManager()->persist($entity); 

    try { 
     // Trigger preFlush event, pass along Entity. Other Listeners can subscribe to this name. 
     $this->getEventManager()->trigger(
      'entity.preFlush', 
      null, 
      [get_class($entity) => $entity] // key = "Address\Entity\Address" for use case 
     ); 

     $this->getEntityManager()->flush(); 
    } catch (\Exception $e) { 
     // Error thrown 
    } 
    // Success stuff, like a trigger "entity.postFlush" 
} 

所以是的。目前在如何实现它的工作方面有点不知所措。

任何帮助将非常感激,并会喜欢解释为什么它是一个解决方案的原因。这真的会帮助我做出更多的这些服务:)

回答

0

已经在它一段时间,但已设法弄清楚为什么它不工作。我附上Listener s到EventManager s,但本来应该将它们附加到SharedEventManager。这是因为我在AbstractActionController中有触发器(在这种情况下),因此它们在实例化时都创建了自己的EventManager(因为它们是唯一的)。

一直是一个艰难的几天,我的包裹周围所有的头,但this article帮了我最多的,或许它只是使事情点击与我的问题的原创性研究和随后的审讯&误差+调试。

下面的代码,因为它是现在,按工作顺序。随着代码的出现,我会尽力解释如何理解它的工作原理。如果我在某个时候出错了,我希望有人纠正我。


先上去,我们需要一个Listener,其注册的组件和事件,以“倾听”为他们触发类。 (他们倾听特定事件触发某些事件)

实现很快就出现了,几乎每个听众都需要$listeners = [];detach(EventManagerInterface $events){...}函数。所以我创建了一个AbstractListener类。

namespace Mvc\Listener; 

use Zend\EventManager\EventManagerInterface; 
use Zend\EventManager\ListenerAggregateInterface; 

/** 
* Class AbstractListener 
* @package Mvc\Listener 
*/ 
abstract class AbstractListener implements ListenerAggregateInterface 
{ 
    /** 
    * @var array 
    */ 
    protected $listeners = []; 

    /** 
    * @param EventManagerInterface $events 
    */ 
    public function detach(EventManagerInterface $events) 
    { 
     foreach ($this->listeners as $index => $listener) { 
      if ($events->detach($listener)) { 
       unset($this->listeners[$index]); 
      } 
     } 
    } 
} 

大约有使用SharedEventManager,并创造了AbstractListener上述实现后,AddressListener类已经结束了,像这样。

namespace Address\Listener; 

use Address\Event\AddressEvent; 
use Admin\Address\Controller\AddressController; 
use Mvc\Listener\AbstractListener; 
use Zend\EventManager\EventManagerInterface; 

/** 
* Class AddressListener 
* @package Address\Listener 
*/ 
class AddressListener extends AbstractListener 
{ 
    /** 
    * @param EventManagerInterface $events 
    */ 
    public function attach(EventManagerInterface $events) 
    { 
     $sharedManager = $events->getSharedManager(); 
     $sharedManager->attach(AddressController::class, 'entity.postPersist', [new AddressEvent(), 'addCoordinatesToAddress']); 
    } 
} 

与附着事件EventManager相对于SharedEventManager的主要区别在于,对于一​​个特定的类后者监听发射触发。在这种情况下,它将侦听AddressController::class发出触发entity.postPersist。在“听到”它被触发后,它会调用一个回调函数。在这种情况下,使用此数组参数注册:[new AddressEvent(), 'addCoordinatesToAddress'],这意味着它将使用类AddressEvent和函数addCoordinatesToAddress

要测试这是否正常工作,并且如果您正在处理此答案,您可以在自己的Controller中创建触发器。我一直在AbstractActionControlleraddAction中工作,被AddressController调用。低于触发上面监听器:

if ($form->isValid()) { 
    $entity = $form->getObject(); 

    $this->getEntityManager()->persist($entity); 

    $this->getEventManager()->trigger(
     'entity.postPersist', 
     $this, 
     [get_class($entity) => $entity] 
    ); 

    try { 
     $this->getEntityManager()->flush(); 
    } catch (\Exception $e) { 
     // Error stuff 
    } 
    // Remainder of function 
} 

在上面的代码中->trigger()功能有以下参数的用法:

  • “entity.postPersist” - 这是事件名称
  • $ this - 这是调用事件的“组件”或对象。在这种情况下,它将是Address\Controller\AddressController
  • [get_class($ entity)=> $ entity] - 这些是与此Event对象一起发送的参数。它会导致您有可用的$event->getParams()[Address::class],它将具有$entity值。

前两个参数将触发SharedEventManager中的监听器。为了测试它是否全部有效,可以修改监听器的附加功能。

它修改到这一点,并在监听器中创建一个函数,所以你可以看到它的工作:

public function attach(EventManagerInterface $events) 
{ 
    $sharedManager = $events->getSharedManager(); 
    $sharedManager->attach(AddressController::class, 'entity.postPersist', [$this, 'test']); 
} 

public function test(Event $event) 
{ 
    var_dump($event); 
    exit; 
} 

最后,为了确保上述实际工作中,监听器必须与EventManager注册。这发生在模块Module.php文件中的onBootstrap函数中(在这种情况下为地址)。注册如下。

public function onBootstrap(MvcEvent $e) 
{ 
    $eventManager = $e->getApplication()->getEventManager(); 
    $eventManager->attach(new AddressListener()); 
} 

如果您在AbstractActionController调试addAction的代码,看到它通过触发器和下次你在test功能的时候,那么你的作品监听。

上面的代码还意味着AddressListener类可以用于连接多个侦听器。所以你也可以注册东西entity.prePersist,entity.preFlush,entity.postFlush和其他你能想到的东西。

接下来,将侦听器恢复到它在开始时的状态(恢复attach功能并删除test功能)。

我也注意到几乎每个Event处理类都需要能够设置并获得EventManager。因此,为此我创建了一个AbstractEvent类,如下所示。

namespace Mvc\Event; 

use Zend\EventManager\EventManager; 
use Zend\EventManager\EventManagerAwareInterface; 
use Zend\EventManager\EventManagerInterface; 

abstract class AbstractEvent implements EventManagerAwareInterface 
{ 
    /** 
    * @var EventManagerInterface 
    */ 
    protected $events; 

    /** 
    * @param EventManagerInterface $events 
    */ 
    public function setEventManager(EventManagerInterface $events) 
    { 
     $events->setIdentifiers([ 
      __CLASS__, 
      get_class($this) 
     ]); 

     $this->events = $events; 
    } 

    /** 
    * @return EventManagerInterface 
    */ 
    public function getEventManager() 
    { 
     if (!$this->events) { 
      $this->setEventManager(new EventManager()); 
     } 

     return $this->events; 
    } 
} 

说实话,我不太清楚为什么我们在setEventManager函数中设置了2个标识符。但足以说它用于注册事件回调。 (如果有人觉得倾斜,以提供它这个可以使用更多/详细的说明)

AddressListener我们试图调用AddressEvent类的addCoordinatesToAddress功能。所以我们将不得不创建它,我做了如下。

namespace Address\Event; 

use Address\Entity\Address; 
use Address\Service\GoogleGeocodingService; 
use Country\Entity\Coordinates; 
use Mvc\Event\AbstractEvent; 
use Zend\EventManager\Event; 
use Zend\EventManager\Exception\InvalidArgumentException; 

class AddressEvent extends AbstractEvent 
{ 
    public function addCoordinatesToAddress(Event $event) 
    { 
     $params = $event->getParams(); 
     if (!isset($params[Address::class]) || !$params[Address::class] instanceof Address) { 

      throw new InvalidArgumentException(__CLASS__ . ' was expecting param with key ' . Address::class . ' and value instance of same Entity.'); 
     } 

     /** @var Address $address */ 
     $address = $params[Address::class]; 

     if (!$address->getCoordinates() instanceof Coordinates) { 
      /** @var GoogleGeocodingService $geocodingService */ 
      $geocodingService = $event->getTarget()->getEvent()->getApplication()->getServiceManager()->get(GoogleGeocodingService::class); 
      $geocodingService->addCoordinatesToAddress($address); 
     } 

     $params = compact('address'); 
     $this->getEventManager()->trigger(__FUNCTION__, $this, $params); 
    } 
} 

在上面你可以看到,首先,我们检查,如果我们希望参数已经与Event $event参数一起传递。我们知道我们应该期待什么以及密钥应该具有什么名字,所以我们明确地进行检查。

下一步,我们检查,如果接收到的Address实体对象已有与之关联的Coordinates对象,如果没有,我们所说的服务做到这一点。

if()声明已运行之后,我们再次启动了trigger。我们沿着这个Event对象和参数传递。最后一步不是必需的,但如果你想链接事件,可以很方便。


在问题中我提到了一个用例。上述代码使ServiceGoogleGeocodingService)能够通过它的要求,并与Factory的配置结合使用,它通过Zend Magic通过ServiceManager创建。

添加一个新的Coordinates对象到现有的Address对象的代码没有被修改,所以我不会将它作为答案的一部分,您可以在问题中找到它。