ZF2 - 如何监听事件并因此触发服务?
ZF2 - How to Listen for Events and Trigger a Service because of it?
一直在尝试学习如何实现服务,因为它们会被侦听器触发。 Have been doing a serious lot of reading the last 用了几天时间让它开始工作,但一直觉得很难。因此,我认为我对事物顺序的理解可能存在缺陷。
The use case I'm trying to get to work is the following:
Just before an Address Entity (with Doctrine, but that's not
important) gets saved (flushed), a Service must be triggered to check
if the Coordinates for the Address are set, and if not, create and
fill a new Coordinates Entity and link it to the Address. The
Coordinates are to be gotten from Google Maps Geocoding API.
将在下面展示我对事物的理解以及理解方式,希望我能说清楚。据我所知,将分步显示中间添加的代码,并告诉您哪些有效,哪些无效。
现在,我对过去几天获得的所有信息的理解是:
监听器必须在 ZF2 的 ServiceManager 中注册。 (Shared)EventManager 的侦听器 "attaches" 某些条件。 EventManager 对一个对象来说是唯一的,但是 SharedEventManager 在应用程序中是 'global'。
在地址模块的 Module.php
class 中,我添加了以下函数:
/**
* @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()
函数,我希望 Listener 可以作为触发事件的垫脚石。但是,这似乎不起作用,因为没有任何东西被触发。不是 addressCreated()
函数,也不是 GoogleCoordinatesService
中的 getCoordinates
函数。
上面的代码应该触发 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,
],
],
Factory 是非常标准的 ZF2 东西,但要描绘完整的画面,这里是 GoogleCoordinatesServiceFactory.php
class。 (已删除 comments/typehints/etc)
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
class。然而,那里没有任何东西被触发执行。因为它甚至没有被调用,所以我确定问题出在上面的代码中,但无法找出原因。根据我阅读和尝试过的内容,我期望应该通过工厂调用 class 本身,并且应该触发 getCoordinates
函数。
所以,class。我删除了一堆标准 getters/setters、注释、文档块和类型提示以使其更短。
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"
}
是的。目前对如何让它工作有点不知所措。
任何帮助将不胜感激,并希望得到关于 "why" 解决方案有效的解释。这真的会帮助我制作更多这些服务:)
已经研究了一段时间,但设法弄清楚了为什么它不起作用。我将 Listener
s 附加到 EventManager
s,但应该将它们附加到 SharedEventManager
。这是因为我在 AbstractActionController
中有触发器(在本例中),因此它们在实例化时都会创建自己的 EventManager
(因为它们是唯一的)。
几天来我一直在思考这一切,但 this article 对我的帮助最大,或者它只是让我对问题的最初研究以及随后的试错 + 调试变得很顺利.
下面的代码是现在的状态,处于工作状态。随着代码的出现,我将尝试解释我是如何理解它的工作原理的。如果我在某些时候说错了,我希望有人纠正我。
首先,我们需要一个 Listener
,一个 class,它将组件和事件注册到 "listen" 以便它们触发。 (他们监听某些(命名的)对象以触发某些事件)
很快意识到几乎每个 Listener 都需要 $listeners = [];
和 detach(EventManagerInterface $events){...}
函数。所以我创建了一个 AbstractListener
class.
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
class 就这样结束了。
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
的主要区别在于后者监听特定的 class 以发出触发器。在这种情况下,它将侦听 AddressController::class
以发出触发器 entity.postPersist
。在 "hearing" 它被触发后,它将调用回调函数。在本例中,它使用此数组参数注册:[new AddressEvent(), 'addCoordinatesToAddress']
,这意味着它将使用 class AddressEvent
和函数 addCoordinatesToAddress
。
要测试这是否有效,如果您正在处理这个答案,您可以在您自己的控制器中创建触发器。我一直在 AbstractActionController
的 addAction
工作,它被 AddressController
的 addAction
调用。在上面监听器的触发器下面:
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 - 这是调用事件的 "component" 或对象。在这种情况下,它将是
Address\Controller\AddressController
- [get_class($entity) => $entity] - 这些是与此
Event
对象一起发送的参数。它将使您有可用的 $event->getParams()[Address::class]
,它将具有 $entity
值。
前两个参数会触发SharedEventManager
中的Listener。要测试是否一切正常,可以修改监听器的附加函数。
将其修改为此并在 Listener 中创建一个函数,以便您可以看到它的工作:
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
class 可以用来附加多个监听器。因此,您还可以为 entity.prePersist
、entity.preFlush
、entity.postFlush
以及您能想到的任何其他内容注册内容。
接下来,将 Listener 恢复到开始时的状态(恢复 attach
函数并删除 test
函数)。
我还注意到几乎每个 Event
处理 class 都需要能够设置和获取 EventManager
。因此,为此我创建了一个 AbstractEvent
class,如下所示。
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 个标识符。但足以说明它用于为事件注册回调。 (如果有人愿意提供,可以使用 more/detailed 解释)
在 AddressListener
中,我们尝试调用 AddressEvent
class 的 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
对象和参数。这最后一步不是必需的,但如果您希望链接事件,它会很方便。
在问题中我提到了一个用例。上面的代码使 Service
(GoogleGeocodingService
) 能够满足它的要求,并结合工厂的配置,通过 Zend Magic 使用 ServiceManager
创建它。
向现有 Address
对象添加新的 Coordinates
对象的代码未修改,因此我不会将其作为答案的一部分,您可以在问题中找到它。
一直在尝试学习如何实现服务,因为它们会被侦听器触发。 Have been doing a serious lot of reading the last 用了几天时间让它开始工作,但一直觉得很难。因此,我认为我对事物顺序的理解可能存在缺陷。
The use case I'm trying to get to work is the following:
Just before an Address Entity (with Doctrine, but that's not important) gets saved (flushed), a Service must be triggered to check if the Coordinates for the Address are set, and if not, create and fill a new Coordinates Entity and link it to the Address. The Coordinates are to be gotten from Google Maps Geocoding API.
将在下面展示我对事物的理解以及理解方式,希望我能说清楚。据我所知,将分步显示中间添加的代码,并告诉您哪些有效,哪些无效。
现在,我对过去几天获得的所有信息的理解是:
监听器必须在 ZF2 的 ServiceManager 中注册。 (Shared)EventManager 的侦听器 "attaches" 某些条件。 EventManager 对一个对象来说是唯一的,但是 SharedEventManager 在应用程序中是 'global'。
在地址模块的 Module.php
class 中,我添加了以下函数:
/**
* @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()
函数,我希望 Listener 可以作为触发事件的垫脚石。但是,这似乎不起作用,因为没有任何东西被触发。不是 addressCreated()
函数,也不是 GoogleCoordinatesService
中的 getCoordinates
函数。
上面的代码应该触发 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,
],
],
Factory 是非常标准的 ZF2 东西,但要描绘完整的画面,这里是 GoogleCoordinatesServiceFactory.php
class。 (已删除 comments/typehints/etc)
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
class。然而,那里没有任何东西被触发执行。因为它甚至没有被调用,所以我确定问题出在上面的代码中,但无法找出原因。根据我阅读和尝试过的内容,我期望应该通过工厂调用 class 本身,并且应该触发 getCoordinates
函数。
所以,class。我删除了一堆标准 getters/setters、注释、文档块和类型提示以使其更短。
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"
}
是的。目前对如何让它工作有点不知所措。
任何帮助将不胜感激,并希望得到关于 "why" 解决方案有效的解释。这真的会帮助我制作更多这些服务:)
已经研究了一段时间,但设法弄清楚了为什么它不起作用。我将 Listener
s 附加到 EventManager
s,但应该将它们附加到 SharedEventManager
。这是因为我在 AbstractActionController
中有触发器(在本例中),因此它们在实例化时都会创建自己的 EventManager
(因为它们是唯一的)。
几天来我一直在思考这一切,但 this article 对我的帮助最大,或者它只是让我对问题的最初研究以及随后的试错 + 调试变得很顺利.
下面的代码是现在的状态,处于工作状态。随着代码的出现,我将尝试解释我是如何理解它的工作原理的。如果我在某些时候说错了,我希望有人纠正我。
首先,我们需要一个 Listener
,一个 class,它将组件和事件注册到 "listen" 以便它们触发。 (他们监听某些(命名的)对象以触发某些事件)
很快意识到几乎每个 Listener 都需要 $listeners = [];
和 detach(EventManagerInterface $events){...}
函数。所以我创建了一个 AbstractListener
class.
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
class 就这样结束了。
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
的主要区别在于后者监听特定的 class 以发出触发器。在这种情况下,它将侦听 AddressController::class
以发出触发器 entity.postPersist
。在 "hearing" 它被触发后,它将调用回调函数。在本例中,它使用此数组参数注册:[new AddressEvent(), 'addCoordinatesToAddress']
,这意味着它将使用 class AddressEvent
和函数 addCoordinatesToAddress
。
要测试这是否有效,如果您正在处理这个答案,您可以在您自己的控制器中创建触发器。我一直在 AbstractActionController
的 addAction
工作,它被 AddressController
的 addAction
调用。在上面监听器的触发器下面:
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 - 这是调用事件的 "component" 或对象。在这种情况下,它将是
Address\Controller\AddressController
- [get_class($entity) => $entity] - 这些是与此
Event
对象一起发送的参数。它将使您有可用的$event->getParams()[Address::class]
,它将具有$entity
值。
前两个参数会触发SharedEventManager
中的Listener。要测试是否一切正常,可以修改监听器的附加函数。
将其修改为此并在 Listener 中创建一个函数,以便您可以看到它的工作:
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
class 可以用来附加多个监听器。因此,您还可以为 entity.prePersist
、entity.preFlush
、entity.postFlush
以及您能想到的任何其他内容注册内容。
接下来,将 Listener 恢复到开始时的状态(恢复 attach
函数并删除 test
函数)。
我还注意到几乎每个 Event
处理 class 都需要能够设置和获取 EventManager
。因此,为此我创建了一个 AbstractEvent
class,如下所示。
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 个标识符。但足以说明它用于为事件注册回调。 (如果有人愿意提供,可以使用 more/detailed 解释)
在 AddressListener
中,我们尝试调用 AddressEvent
class 的 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
对象和参数。这最后一步不是必需的,但如果您希望链接事件,它会很方便。
在问题中我提到了一个用例。上面的代码使 Service
(GoogleGeocodingService
) 能够满足它的要求,并结合工厂的配置,通过 Zend Magic 使用 ServiceManager
创建它。
向现有 Address
对象添加新的 Coordinates
对象的代码未修改,因此我不会将其作为答案的一部分,您可以在问题中找到它。