EventDispatcher组件提供了一个让您的应用程序组件派遣事件和监听他们的工具。
介绍
为了确保代码的可扩展性,面向对象的代码已经走了很长的路。通过创建具有明确职责的类,来让你的代码变得更加灵活,并且开发人员可以通过子类继承他们来修改他们的行为。如果他们想要与其他开发人员分享这些变化,再继承代码就不行了,他们只能创建自己的子类。
考虑到这是个真实的例子,我们需要给项目提供一个插件系统。作为一个插件他应该能够添加方法,或者在一些事情之前添加方法,或者在方法之后执行,并不与其他插件干扰。这并不是一个容易解决的问题,单个或多重继承(均可以使用php)有很多缺点,与它们相比要复杂的多。
- 一个listener 监听器(php对象)告诉一个中央dispatcher(派遣)对象,他想去监听kernel.response事件。
- 在某些时候,symfony内核会告诉dispatcher对象去派遣kernel.response事件,传递这个Event对象,来访问
Response对象。
- 这个派遣器会通知(例如,调用一个方法)
kernel.response事件的所有监听,让他们每个都去修改Response对象。
安装
- 通过composer安装(
symfony/event-dispatcher
在Packagist上) - 使用官方的git库(https://github.com/symfony/event-dispatcher)
使用
事件
当时间被派遣,他就是一个唯一的标识名称(例如kernel.response),可能有很多的监听器在监听它。一个Event实例也会被创建并传入到这些监听中。你稍后会看到,这些Event对象通常包含被派遣(dispatched)事件的数据。
命名规范
这个唯一的事件名可以是任何字符串,但你可以选择以下几个简单的命名规范:
- 使用小写字母,数字,点(.)或下划线(_);
- 命名空间前缀名称后跟一个点(例如 kernel.);
- 结尾名称,是一个动词是正在采用的动作(如request);
这里有一些很标准的事件名称:
- kernel.response
- form.pre_set_data
事件名称和事件对象
当派遣器通知监听器,传入一个普通的Event对象到监听器。这个基础的Event类非常的简单:它包含一个停止事件方法( event propagation),仅此而已。
很多时候,监听器需要事件对象附带传入一些需要的数据信息。这个案例中的kernel.response事件,每一个监听实际上会创建并传入一个 FilterResponseEvent事件对象,这个对象是基于Event的一个子类。这个类包含getResponse和setResponse方法,允许监听去获取,甚至是替换这个Response对象。
这个寓意是:当我们给一个事件创建一个监听器时,这个事件会传入到监听器,这个事件可能是一个子类并且他还可能有一些额外的检索信息的方法并响应这个事件。
Dispatcher(派遣器)
这个Dispatcher是event dispatcher 系统的核心对象。通常,创建一个简单的派遣器,要保证已经注册了一个监听器。当一个事件通过派遣器派遣,他会通知这个事件注册的所有监听器:
1 2 3 |
use Symfony\Component\EventDispatcher\EventDispatcher; $dispatcher = new EventDispatcher(); |
连接监听器
要使用现有的事件,你需要一个监听器到派遣器有一个连接,以便监听能够在事件被派遣时得到通知。调用派遣器中的addListener()方法关联任何有效的php callable(php回调类型-传入监听方法)到一个事件:
1 2 |
$listener = new AcmeListener(); $dispatcher->addListener('foo.action', array($listener, 'onFooAction')); |
这个addListener()方法接受三个参数:
- 这个监听器想要去监听的事件名称(string字符串)
- php callable(php回调类型-传入监听方法):抛出一个事件,监听这个事件的监听器的方法就会被通知。
-
一个可选项主要用来管理监听器触发的优先级(默认为0),传入参数为整数(越高越重要,监听器也会越早触发),如果两个监听器有相同的优先级,那么他们会按照派遣器的添加顺序去执行。
注释: PHP callable 是一个php变量被call_user_func()函数使用,并当传入到is_callable函数时返回ture。他可以是一个\Closure实例,一个对象实现一个__invoke方法(实际上就是闭包),一个字符串代表一个函数,或者一个数组代表一个对象方法或者类方法。
到目前为止,你已经看到了php对象如何注册为一个监听。你也可以注册一个php闭包( Closures )作为一个事件监听:
12345 use Symfony\Component\EventDispatcher\Event;$dispatcher->addListener('foo.action', function (Event $event) {// will be executed when the foo.action event is dispatched});
一旦监听器被注册到派遣器,他就会等待事件被触发。在上面的例子中,当这个foo.action事件被派遣,这个派遣器会调用AcmeListener:onFooAction方法并传入Event对象作为参数:
1 2 3 4 5 6 7 8 9 10 11 |
use Symfony\Component\EventDispatcher\Event; class AcmeListener { // ... public function onFooAction(Event $event) { // ... do something } } |
在很多情况下,一个事件会带上很多的特定方法,我们管他叫特定事件,一个继承事件的特定事件子类会被传入到监听器。这个监听器就可以访问这些关于传入事件的特定的信息了。然后,检查确定传入的每一个事件是要是Symfony\Component\EventDispatcher\Event实例。例如,kernel.response事件传入的实例是Symfony\Component\HttpKernel\Event\FilterResponseEvent
:
1 2 3 4 5 6 7 8 9 |
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); $request = $event->getRequest(); // ... } |
扩展:在服务容器注册事件监听
当你使用
ContainerAwareEventDispatcher和DependencyInjection component时,你可以使用RegisterListenersPass去标记服务作为一个事件监听:
123456789101112131415161718192021222324252627 use Symfony\Component\DependencyInjection\ContainerBuilder;use Symfony\Component\DependencyInjection\Definition;use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;use Symfony\Component\DependencyInjection\Reference;use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;$containerBuilder = new ContainerBuilder(new ParameterBag());$containerBuilder->addCompilerPass(new RegisterListenersPass());// register the event dispatcher service$containerBuilder->setDefinition('event_dispatcher', new Definition('Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher',array(new Reference('service_container'))));// register your event listener service$listener = new Definition('AcmeListener');$listener->addTag('kernel.event_listener', array('event' => 'foo.action','method' => 'onFooAction',));$containerBuilder->setDefinition('listener_service_id', $listener);// register an event subscriber$subscriber = new Definition('AcmeSubscriber');$subscriber->addTag('kernel.event_subscriber');$containerBuilder->setDefinition('subscriber_service_id', $subscriber);默认情况下,假设“event_dispatcher”是事件派遣的id,这个监听器传入到事件派遣中。其中kernel.event_listener标签代表event listeners,kernel.event_subscriber标签代表event subscribers。你能够在RegisterListenersPass的构造函数中传入自定义的值来改变默认值。
创建和派遣一个事件
除了监听现有的事件,你还可以创建和派遣自己的事件。当你创建第三方库或者你想去让你的系统的不同组件保持灵活和松耦合,他的用处是很大的。
静态事件类
假设你想创建一个新的事件-store.order-在你的应用中任何时间派遣都会创建一个订单。为了让这件事井然有序,一开始就要在你的应用程序中创建一个StoreEvents类,来定义服务和记录你的事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace Acme\StoreBundle; final class StoreEvents { /** * The store.order event is thrown each time an order is created * in the system. * * The event listener receives an * Acme\StoreBundle\Event\FilterOrderEvent instance. * * @var string */ const STORE_ORDER = 'store.order'; |
请注意这个类并不会做任何事情。StoreEvents类的目的是成为公共事件的一个位置标记信息。也要注意到一个特殊FilterOrderEvent类将被传递给每个监听器。
创建一个事件对象
后面,当你派遣这个新的事件,你需要创建一个Event实例并把他传入到派遣器。这个派遣器会将这个相同的实例传入到每个事件的监听器中。如果你不需要传入任何信息到你的监听器,你可以使用默认的Symfony\Component\EventDispatcher\Event类。大多数时间,你需要传入关于Event的信息到监听器。要做到这一点,你就要创建一个新的类去继承Symfony\Component\EventDispatcher\Event
.
在这个例子中,每个监听器都需要访问一些Order对象。创建一个Event类来完成这种假设:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\Event; use Acme\StoreBundle\Order; class FilterOrderEvent extends Event { protected $order; public function __construct(Order $order) { $this->order = $order; } public function getOrder() { return $this->order; } } |
现在每个监听器都可以访问这个Order对象的getOrder方法。
派遣这个事件
这个dispatch()方法触发事件的所有监听。他又两个参数:派遣的事件名称,Event实例(每个监听器需要传入的事件):
1 2 3 4 5 6 7 8 9 10 11 |
use Acme\StoreBundle\StoreEvents; use Acme\StoreBundle\Order; use Acme\StoreBundle\Event\FilterOrderEvent; // the order is somehow created or retrieved $order = new Order(); // ... // create the FilterOrderEvent and dispatch it $event = new FilterOrderEvent($order); $dispatcher->dispatch(StoreEvents::STORE_ORDER, $event); |
注意,这个特殊的FilterOrderEvent对象会被创建和传入到dispatch方法。现在,任何监听器去监听这个store.order 事件都能够收到FilterOrderEvent并且还能够访问Order对象的getOrder方法:
1 2 3 4 5 6 7 8 |
// some listener class that's been registered for "store.order" event use Acme\StoreBundle\Event\FilterOrderEvent; public function onStoreOrder(FilterOrderEvent $event) { $order = $event->getOrder(); // do something to or with the order } |
使用事件订阅器
最常见的方式就是监听(一个在派遣器中注册的event listener的事件)。这个监听器能够监听一个或者多个事件并且这些事件被派遣时会通知这些事件。
有另一种方式来监听事件,那就是event subscriber.一个事件订阅器就是一个php类他能够告诉派遣器究竟应该订阅那些事件。他实现了 EventSubscriberInterface接口,这个接口需要实现一个简单的静态方法getSubscribedEvents.下面例子就是一个订阅者去订阅
kernel.response和store.order事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; class StoreSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( 'kernel.response' => array( array('onKernelResponsePre', 10), array('onKernelResponseMid', 5), array('onKernelResponsePost', 0), ), 'store.order' => array('onStoreOrder', 0), ); } public function onKernelResponsePre(FilterResponseEvent $event) { // ... } public function onKernelResponseMid(FilterResponseEvent $event) { // ... } public function onKernelResponsePost(FilterResponseEvent $event) { // ... } public function onStoreOrder(FilterOrderEvent $event) { // ... } } |
这非常类似于一个侦听器类,不同的是这个类自己能够告诉这个派遣器那些事件被监听。去在派遣器中注册一个订阅器,使用 addSubscriber()方法:
1 2 3 4 |
use Acme\StoreBundle\Event\StoreSubscriber; $subscriber = new StoreSubscriber(); $dispatcher->addSubscriber($subscriber); |
这个派遣器将自动注册订阅者(通过getSubscribedEvents方法,返回每个事件的订阅者)。这个方法通过事件名称返回一个数组索引,他们的值就是调用方法的名称或者是方法名称组成一个数组,还有优先级。上面的例子说明如何在订阅器中注册同一个事件到多个监听器方法,也展示了如何传入优先级到每一个监听方法中。优先级越高就越早会被调用。在上面的例子中,当kernel.response事件被触发,该onKernelResponsePre, onKernelResponseMid和
onKernelResponsePost
方法会按照顺序调用。
停止事件 Flow/Propagation
在某些情况下,可能有一个监听器,不乐意让其他的监听器调用。换句话说,监听器需要能够告诉派遣器停止所有未来的事件监听器(即不再通知监听器)。从一个监听中使用stopPropagation()方法来完成:
1 2 3 4 5 6 7 8 |
use Acme\StoreBundle\Event\FilterOrderEvent; public function onStoreOrder(FilterOrderEvent $event) { // ... $event->stopPropagation(); } |
现在,任何没有调用store.order的监听器将不会被调用。
可以检测这个事件是否使用了isPropagationStopped()方法,他会返回boolean值:
1 2 3 4 |
$dispatcher->dispatch('foo.event', $event); if ($event->isPropagationStopped()) { // ... } |
EventDispatcher Aware Events and Listeners
这个EventDispatcher总是会传入要派遣的事件,传入事件名称和事件需要的监听器。你还可以使用一些EventDispatcher的高级用法,例如在监听器中派遣其他事件,事件连接或者甚至是懒加载多个监听到派遣器对象,如下面例子所示:
懒加载监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Acme\StoreBundle\Event\StoreSubscriber; class Foo { private $started = false; public function myLazyListener( Event $event, $eventName, EventDispatcherInterface $dispatcher ) { if (false === $this->started) { $subscriber = new StoreSubscriber(); $dispatcher->addSubscriber($subscriber); } $this->started = true; // ... more code } } |
从一个监听器中派遣其他事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo { public function myFooListener( Event $event, $eventName, EventDispatcherInterface $dispatcher ) { $dispatcher->dispatch('log', $event); // ... more code } } |
虽然上面足以满足大多数的应用,如果你的应用程序使用多个事件派遣(EventDispatcher)实例,您可能需要专门的注入一个已知的EventDispatcher实例到你的监听器。你能够通过使用构造函数和setter注入法完成:
构造器注入:
1 2 3 4 5 6 7 8 9 10 11 |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo { protected $dispatcher = null; public function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } } |
setter注入:
1 2 3 4 5 6 7 8 9 10 11 |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo { protected $dispatcher = null; public function setEventDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } } |
这两者之间做出选择实在是一种习惯问题。许多人往往喜欢在构造函数注入对象,你初始化时就已经完成了工作。但是,当有一大串的依赖,使用setter是你最好的选择,其是对可选依赖。、
派遣器快捷方式
EventDispatcher::dispatch方法总会返回一个Event对象。他允许使用快捷方式。例如,如果不需要一个定制的事件对象,依赖一个简单的
Event对象。你甚至都不需要把他传递给派遣器程序,因为默认他会创建一个,除非你传入的是一个特殊事件。
1 |
$dispatcher->dispatch('foo.event'); |
此外,这个事件派遣始终返回派遣的事件对象,例如这个传入的事件或者是通过派遣器内部创建的事件。他们都允许快捷键:
1 2 3 |
if (!$dispatcher->dispatch('foo.event')->isPropagationStopped()) { // ... } |
或者:
1 2 |
$barEvent = new BarEvent(); $bar = $dispatcher->dispatch('bar.event', $barEvent)->getBar(); |
或者:
1 |
$bar = $dispatcher->dispatch('bar.event', new BarEvent())->getBar(); |
等等……
Event Name Introspection
这个EventDispatcher实例,以及事件派遣的名称,作为参数传递给监听器:
1 2 3 4 5 6 7 8 9 10 |
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Foo { public function myEventListener(Event $event, $eventName, EventDispatcherInterface $dispatcher) { // ... do something with the event name } } |
其他派遣器
除了常用的EventDispatcher,该组件还有两个其他的派遣器: