2017-08-31 112 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對象的代碼沒有被修改,所以我不會將它作爲答案的一部分,您可以在問題中找到它。