Php ZF2-如何侦听事件并因此触发服务?

Php ZF2-如何侦听事件并因此触发服务?,php,event-handling,zend-framework2,Php,Event Handling,Zend Framework2,一直在尝试学习如何实现服务,因为它们是由侦听器触发的。几天时间让它开始工作,但一直觉得很难。因此我认为我对事物顺序的理解可能有缺陷 我正在尝试的用例如下所示: 就在地址实体之前(有条令,但那不是 重要信息)保存(刷新),必须触发服务进行检查 如果地址的坐标已设置,如果未设置,则创建和 填充一个新的坐标实体并将其链接到地址。这个 坐标将从谷歌地图地理编码API获取 下面将展示我是如何理解事物的,希望我能把自己弄清楚。据我所知,我将分步进行,以显示中间添加的代码,并告诉您哪些是有效的,哪些是无效的

一直在尝试学习如何实现服务,因为它们是由侦听器触发的。几天时间让它开始工作,但一直觉得很难。因此我认为我对事物顺序的理解可能有缺陷

我正在尝试的用例如下所示:

就在地址实体之前(有条令,但那不是 重要信息)保存(刷新),必须触发服务进行检查 如果地址的坐标已设置,如果未设置,则创建和 填充一个新的坐标实体并将其链接到地址。这个 坐标将从谷歌地图地理编码API获取

下面将展示我是如何理解事物的,希望我能把自己弄清楚。据我所知,我将分步进行,以显示中间添加的代码,并告诉您哪些是有效的,哪些是无效的

现在,我对过去几天得到的所有信息的理解是:

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

在Address模块的
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...
    }
}
我希望侦听器能够根据
函数attach(…){}
中的
->attach()
函数作为触发事件的跳板。然而,这似乎不起作用,因为不会触发任何事件。不是
addressCreated()
函数,也不是
GoogleCoordinateService
中的
getCoordinates
函数

上面的代码应该触发
GoogleCoordinateService
函数
getCoordinates
。不过,该服务有一些要求,例如EntityManager的存在、它所关注的地址实体和配置

为此,我创建了以下配置

文件
google.config.php
(加载,检查)

module.config.php
中,我向一家工厂注册了该服务

'service_manager' => [
    'factories' => [
        GoogleCoordinatesService::class => GoogleCoordinatesServiceFactory::class,
    ],
],
该工厂是相当标准的ZF2产品,但为了描绘一幅完整的画面,这里是
GoogleCoordinateServiceFactory.php
类。(删除注释/类型提示/等)

下面是
GoogleCoordinateService
类。然而,没有任何东西会被触发在那里执行。由于它甚至没有被调用,我确信问题在于上面的代码,但无法找到原因。根据我所阅读和尝试的内容,我希望通过工厂调用类本身,并触发
getCoordinates
函数

所以,上课。我删除了一堆标准的getter/setter、注释、docblock和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"
}
是的。目前,我对如何让它工作有点茫然


任何帮助都将不胜感激,并希望解释“为什么”解决方案有效。这将真正帮助我更多地利用这些服务:)

已经使用了一段时间,但已经设法找出了它不起作用的原因。我将
Listener
s附加到
EventManager
s,但应该将它们附加到
SharedEventManager
。这是因为我在
AbstractActionController
中有触发器(在本例中),因此它们在实例化时都会创建自己的
EventManager
(因为它们是唯一的)

这几天我一直在绞尽脑汁想这一切,但这对我帮助最大,或许这只是让我在问题上的原始研究和随后的试错+调试过程中取得了一些进展

下面的代码,因为它是现在,在工作秩序。随着代码的出现,我将尝试解释我如何理解它的工作原理。如果我在某个时候弄错了,我希望有人能纠正我


首先,我们需要一个
侦听器
,一个注册组件和事件以“侦听”它们以触发的类。(它们侦听特定(命名)对象以触发特定事件)

很快就意识到几乎每个监听器都需要
$listeners=[]
分离(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

为了测试这是否有效,并且如果您正在使用此答案,您可以在自己的控制器中创建触发器。我一直在
AbstractActionController
addAction
中工作,它被
广告调用
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]);
            }
        }
    }
}
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']);
    }
}
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
}
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;
}
public function onBootstrap(MvcEvent $e)
{
    $eventManager = $e->getApplication()->getEventManager();
    $eventManager->attach(new AddressListener());
}
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;
    }
}
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);
    }
}