Symfony2学习笔记之事件分配器


----EventDispatcher组件使用html

简介:编程

      面向对象编程已经在确保代码的可扩展性方面走过了很长一段路。它是经过建立一些责任明确的类,让它们之间变得更加灵活,开发者能够经过继承这些类建立子类,来改变它们的行为。可是若是想将某个开发者的改变跟其它已经编写了本身子类的开发者共享,这种面向对象的继承就再也不那么有用了。数组

      举一个现实的实例,你想为你的项目提供一个插件系统。一个可以被添加到方法的插件,或者在方法执行的先后完成某些工做,而不会干扰到其它插件。这个经过单一的继承完成不是一个容易的事情,多重继承又有它的局限性。缓存

      SF2的Event Dispathcer组件经过一个简单有效的方式实现了Mediator模式,让这些需求的实现成为可能并为你的项目带来了真正的扩展性。函数

      从HttpKernel组件的示例提及,一旦一个Response对象被建立,可以让系统中其它元素在该Response对象被真正使用以前修改它将是很是有用的。(好比添加一个缓存的头),SF2内核经过一个事件kernel.response作到了这一点.性能

那么它是如何工做的呢?
一个listener告诉中心dispatcher对象它想监听kernel.response事件:
在某个点上,SF2核心告诉dispatcher对象分配kernel.response事件,同时传递一个Event对象给分配的目标对象。
该Event对象能够用于访问Response对象。
Dispatcher通知全部监听kernel.response事件的监听者,容许它们对Response对象进行修改。ui


若是一个事件要被分配,它必须有一个可以标识本身的惟一名字(好比:kernel.response),这样任意数量的监听者均可以注册监听该名字。在分配过程当中,同时会建立一个Event实例传递给全部的监听者。该Event对象自己一般会包含一些关于被分配事件的数据。this

关于事件的名字能够是任意字符串,可是一般遵循以下的规则:
只使用小写字符,数字和点号以及下划线。
用命名空间名加点号做为前缀。
一般以指定发生行为的动词做为名字的结尾(好比request).spa

以下的定义时合法的事件名:
kernel.response
form.pre_set_data插件


事件的名称和具体事件对象:
当Dispatcher通知一个监听者时,它会传递一个真正的Event对象给这些监听者。Event基类很是简单,它除了包含一个用于中止事件传递的方法外,其它什么都没有。

一般特定事件的数据须要和该事件一块儿被传递给监听者,让监听该事件的监听者拥有足够的信息来响应事件。好比在kernel.response事件中,一个Event对象被建立并传递给了监听它的每一位监听者,该Event实例的实际类型是FilterResponseEvent,是Event基类的一个子类。该类包含了像getResponse()和setResponse()类型的方法,容许监听者获取甚至替换Response对象。

这个故事的寓意是,当建立一个某一事件的监听者时,传递给监听者的Event对象多是其特定的子类,该类有附加的方法来从事件中获取信息并回复该事件。


事件分配器Dispatcher:
它是整个事件分配系统的中心对象。
一般状况下,只有惟一的分配器被建立,它维护者注册于它的全部监听者。
当一个事件经过Dispatcher被分配时,它会通知全部注册监听该事件的监听者。

1 use Symfony\Component\EventDispatcher\EventDispatcher;
2 
3 $dispatcher = new EventDispatcher();

 

将监听者注册到事件分配器:
要使用已有的事件,你须要把事件监听者关联到分配器以便它在分配事件时可以通知它们。
经过在dispatcher上面调用addListener()方法能够将任意的PHP合法调用关联到某个事件。

1 $listener = new AcmeListener();
2 $dispatcher->addListener('foo.action', array($listener,'onFooAction'));

这里addListener方法接收3个参数:

监听者须要监听的事件名称字符串做为第一个参数:
一个监听事件的PHP调用
一个可选参数表明监听程序执行优先级(越大表明越重要),它以为着监听者被触发的顺序,默认值为0。若是两个监听者优先级值相同那么按照其注册顺序执行。


注意:PHP callable是一个PHP变量,它能够被用于call_user_func()方法并当它被传入is_callable()方法时会返回一个true。 它能够是\Closure实例,一个实现了__invoke方法的对象,一个表示一个函数方法的字符串,或者表示一个对象方法或者一个类方法的数组。

到目前为止你知道了那些PHP对象能够被注册为监听者。你还能够注册PHP Closure做为事件监听者:

1 use Symfony\Component\EventDispatcher\Event;
2 
3 $dispatcher->addListener('foo.action',function(Event $event){
4     //该方法将在foo.action事件被分配时执行
5 });

一旦一个监听者被注册到dispatcher,它就会一直等待该事件被通知。

在上面的实例中,当foo.action被分配时,分配器会调用AcmeListener::onFooAction方法并传入Event对象做为惟一参数。

复制代码
 1 use Symfony\Component\EventDispatcher\Event;
 2 
 3 class AcmeListener
 4 {
 5      // ...
 6 
 7      public function onFooAction(Event $event)
 8     {
 9          // ... 相关操做
10      }
11 }
复制代码

在不少状况下则是Event对象的一些子类被传递给指定事件的监听者。这些子类会让监听者可以经过一些附加的方法访问关于该事件的特定信息。咱们一般须要查看SF2提供的文档说明或者事件的实现来决定Event事件触发时须要传入的类。

好比:kernel.event事件传入一个Symfony\Component\HttpKernel\Event\FilterResponseEvent:

复制代码
1 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
2 
3 public function onKernelResponse(FilterResponseEvent $event)
4 {
5     $response = $event->getResponse();
6     $request = $event->getRequest();
7 
8     // ...
9 }
复制代码

 


下面咱们来看看建立并分配一个事件的过程:
咱们除了注册监听者到已有的事件外,咱们还能够建立和监听本身的事件。这对于咱们建立第三方类库或者保持咱们本身系统组件的灵活性和解耦分层有用。

1.首先建立静态事件类:
假设你想建立一个新事件store.order,它将会在每次订单被建立时分配。
为了让其看起来更规范,咱们建立一个StoreEvents类用于定义和说明咱们的事件:

复制代码
 1 namespace Acme\StoreBundle;
 2 
 3 final class StoreEvents
 4 {
 5     /**
 6     * store.order事件会在每次订单被建立时抛出
 7     *
 8     * 监听该事件的监听者会接收到一个
 9     * Acme\StoreBundle\Event\FilterOrderEvent实例
10     *
11     * @var string
12     */
13     const STORE_ORDER = 'store_order';
14 }
复制代码

注意,该类没有作任何实际的工做,它的目的仅仅是定位公用事件信息集中的地方。同时咱们还注意到在注释里说明了一个FilterOrderEvent对象被一同传递给监听者。

2.建立一个Event对象
接下来,当你派遣一个新事件时,你须要建立一个Event实例并传递给dispatcher。dispatcher会传递该实例到每个监听该事件的监听者那里。若是你不须要传递任何信息给这些监听者,你能够直接使用默认的Symfony\Component\EventDispatcher\Event类。
大多时候,你须要传递关于该事件的一些信息给监听者,要完成这个目的,你须要建立一个新的扩展于Symfony\Component\EventDispatcher\Event类的新类。

在该例子中,每一个监听者须要方法一些模拟的Order对象。那么须要建立一个新的Event子类来知足:

复制代码
 1 namespace Acme\StoreBundle\Event;
 2 
 3 use Symfony\Component\EventDispatcher\Event;
 4 use Acme\StoreBundle\Order;
 5 
 6 class FilterOrderEvent extends Event
 7 {
 8     protected $order;
 9 
10     public function __construct(Order $order)
11     {
12         $this->order = $order;
13     }
14 
15     public function getOrder()
16     {
17         return $this->order;
18     }
19 }
复制代码

这样每一个监听者均可以经过该类的getOrder方法来访问订单对象了。

3. 分配事件
dispatch()方法通知全部的给定事件的监听者。它带有两个参数:分配事件的名字和须要传递给每一个监听者的Event实例。

复制代码
 1 use Acme\StoreBundle\StoreEvents;
 2 use Acme\StoreBundle\Order;
 3 use Acme\StoreBundle\Event\FilterOrderEvent;
 4 
 5 // 实例化一个须要的订单对象
 6 $order = new Order();
 7 // ...
 8 
 9 // 建立 FilterOrderEvent 并分配它
10 $event = new FilterOrderEvent($order);
11 $dispatcher->dispatch(StoreEvents::STORE_ORDER, $event);
复制代码

注意,这里是一个特定的FilterOrderEvent对象被建立并传递给该事件的全部监听者,监听者们接收该对象后经过其getOrder方法访问Order对象。

复制代码
1 // 假设有一些监听者被注册到 "STORE_ORDER" 事件
2 use Acme\StoreBundle\Event\FilterOrderEvent;
3 
4 public function onStoreOrder(FilterOrderEvent $event)
5 {
6     $order = $event->getOrder();
7     // 对订单进行一些处理
8 }
复制代码

 

4.使用事件订阅者
最多见的方式是一个事件监听者经过dispatcher注册到某个事件,该监听者能够监听一个或者多个事件而且在每次该事件被分配时得到通知。

另一种监听事件的方式是经过一个事件订阅者来完成。
一个事件订阅者是一个PHP类,它可以告诉dispatcher到底哪些事件应该订阅。
事件订阅者实现了EventSubscriberInterface接口,它惟一须要实现的一个静态方法叫 getSubscribedEvents
下面的示例显示一个事件订阅者订阅kernel.response和store.order事件:

复制代码
 1 namespace Acme\StoreBundle\Event;
 2 
 3 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 4 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
 5 
 6 class StoreSubscriber implements EventSubscriberInterface
 7 {
 8     public static function getSubscribedEvents()
 9     {
10         return array(
11             'kernel.response' => array(
12                 array('onKernelResponsePre', 10),
13                 array('onKernelResponseMid', 5),
14                 array('onKernelResponsePost', 0),
15             ),
16             'store.order' => array('onStoreOrder', 0),
17             );
18     }
19 
20     public function onKernelResponsePre(FilterResponseEvent $event)
21     {
22         // ...
23     }
24 
25     public function onKernelResponseMid(FilterResponseEvent $event)
26     {
27         // ...
28     }
29 
30     public function onKernelResponsePost(FilterResponseEvent $event)
31     {
32         // ...
33     }
34 
35     public function onStoreOrder(FilterOrderEvent $event)
36     {
37         // ...
38     }
39 }                        
复制代码

它很是相似于监听者类,除了该类自己可以告诉dispatcher须要监听哪些事件除外。

要注册一个订阅者到dispatcher,须要使用dispatcher的addSubscriber()方法。

1 use Acme\StoreBundle\Event\StoreSubscriber;
2 
3 $subscriber = new StoreSubscriber();
4 $dispatcher->addSubscriber($subscriber);

这里dispatcher会自动每个订阅者的getSubscribedEvents方法返回的事件。该方法会返回一个以事件名字为索引的数组,它的值既能够是调用的方法名也能够是组合了方法名和调用优先级的数组。

上面的例子显示如何在订阅者类中注册多个监听方法到同一个事件,以及显示了如何为每一个监听方法传入优先级设置。优先级数越高的方法越早被调用。
根据上面示例的定义,当kernel.response事件被分配时,其监听方法的调用顺序依次是:
onKernelResponsePre,OnKernelResponseMid和onKernelResponsePost.

5.阻止事件流/传递
有些状况下,可能有一个监听者来阻止其它监听者被调用。换句话说,监听者须要能告诉dispatcher来阻止将事件传递给后续的监听者。这个能够在一个监听者内部经过stopPropagation()方法来实现。

复制代码
1 use Acme\StoreBundle\Event\FilterOrderEvent;
2 
3 public function onStoreOrder(FilterOrderEvent $event)
4 {
5     // ...
6 
7     $event->stopPropagation();
8 }
复制代码

如今,任何尚未被调用的监听store.order事件的监听者将不会再被调用。

咱们能够经过isPropagationStopped()方法来判断一个事件被阻止。

1 $dispatcher->dispatch('foo.event',$event);
2 if($event->isPropagationStopped()){
3     //..
4 }

 


6.事件分配器知道事件和监听者
EventDispatcher老是注入一个它本身的引用到传入的event对象。这就意味着全部的监听者能够经过Dispatcher传递给本身的Event对象的getDispatcher()方法直接访问EventDispatcher对象。

这些能够致使EventDispatcher的一些高级应用,包括将监听者派遣其它事件,事件链或者更多监听者的事件延迟加载到dispatcher对象。
下面是延迟加载监听者:

复制代码
 1 use Symfony\Component\EventDispatcher\Event;
 2 use Acme\StoreBundle\Event\StoreSubscriber;
 3 
 4 class Foo
 5 {
 6     private $started = false;
 7 
 8     public function myLazyListener(Event $event)
 9     {
10         if(false === $this->started){
11             $subscriber = new StoreSubscriber();
12             $event->getDispatcher()->addSubscriber($subscriber);
13         }
14         $this->started = true;
15 
16         //...更多代码
17     }
18 }
复制代码

 

从一个监听者内部派遣另外的事件:

复制代码
 1 use Symfony\Component\EventDispatcher\Event;
 2 
 3 class Foo
 4 {
 5     public function myFooListener(Event $event)
 6     {
 7         $event->getDispatcher()->dispatch('log',$event);
 8 
 9         //... 更多代码
10     }
11 }
复制代码

 

若是你的应用程序中使用多个EventDispatcher实例,你可能须要专门注入一个已知EventDispatcher实例到你的监听器。这能够经过构造函数或者setter方法注入:

复制代码
 1 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 2 
 3 class Foo
 4 {
 5     protected $dispatcher = null;
 6 
 7     public function __construct(EventDispatcherInterface $dispatcher)
 8     {
 9         $this->dispatcher = $dispatcher;
10     }
11 }
复制代码

setter方法注入:

复制代码
 1 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 2 
 3 class Foo
 4 {
 5     protected $dispatcher = null;
 6 
 7     public function setEventDispatcher(EventDispatcherInterface     $dispatcher)
 8     {
 9         $this->dispatcher = $dispatcher;
10     }
11 }
复制代码

以上两种注入方法选用哪个彻底取决于我的喜爱。一些人倾向于构造器注入,由于在构造时就可以彻底初始化。可是当你有一个很长的依赖名单时,使用setter注入就是个可选的方式,尤为是在依赖项是可选的状况下。


7.分配器的简写使用方式:
EventDispatcher::dispatch方法老是返回一个Event对象。这样就给咱们提供了不少简写的机会。好比一个不须要自定义Event对象的事件,它彻底能够依靠原生的Event对象来派遣,你不须要给dispatch方法传入任何Event对象,它本身会建立一个默认的Event对象来使用。

$dispatcher->dispatch('foo.event');

更深一步,EventDispatcher老是返回被派遣的事件对象,不管是传入的仍是本身内部建立的。

这样咱们就能够作一些美观的简写:

if(!$dispatcher->dispatch('foo.event')->isPropagationStopped()){
    //....
}

或者:

$barEvent = new BarEvent();
$bar = $dispatcher->dispatch('foo.event',$barEvent)->getBar();

又或者:

$response = $dispatcher->dispatch('bar.event', new BarEvent())->getBar();

 


8.事件名称的内部自知
由于EventDispatcher在分配事件过程当中早已经知道了事件的名称,事件名称又是被注入到Event对象中,因此,对于事件监听者来讲彻底能够经过getName()方法获取它。

这样事件名称就能够(和其它在自定义Event中包含的其它数据同样)做为监听者处理事件流程的一部分使用了。

复制代码
use Symfony\Component\EventDispatcher\Event;

class Foo
{
    public function myEventListener(Event $event)
    {
        echo $event->getName();
    }
}
复制代码

 


9.其它类型事件分配器:
服务容器感知的事件分配器 ContainerAwareEventDispatcher 是一个比较特殊的事件分配器实现。它耦合了服务容器,做为依赖注入组件的一部分实现。它容许把服务做为指定事件的监听者,从而让事件分配器具有了极强的性能。

服务在容器中时延迟加载的,这就意味着做为监听者使用的服务只有在一个事件被派遣后须要这些监听者时才被建立。

安装配置比较简单只须要把一个ContainerInterface注入到ContainerAwareEventDispatcher便可:

1 use Symfony\Component\DependencyInjection\ContainerBuilder;
2 use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
3 
4 $container = new ContainerBuilder();
5 $dispatcher = new ContainerAwareEventDispatcher($container);

 

添加监听者:
容器知道事件分配器既能够经过直接加载特定服务,也可经过实现EventSubscriberInterface接口的实现。

下面的示例假设服务勇气已经加载了一些出现的服务:
注意服务必须在容器中标注为public的。

添加服务:
使用addListenerService()方法来链接已存在的服务定义,这里的$callback变量是一个数组:

array($serviceId, $methodName)

$dispatcher->addListenerService($eventName,array('foo','LogListener'));

 

添加订阅者服务:
能够经过addSubscriberService()方法添加EventSubscribers对象,这里第一个参数是订阅者服务ID,第二个参数是服务类的名称(该类必须实现了EventSubscriberInterface接口):

$dispatcher->addSubscriberService(
    'kernel.store_subscriber',
    'StoreSubscriber'
);

EventSubscriberInterface具体实现:

复制代码
 1 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 2 // ...
 3 
 4 class StoreSubscriber implements EventSubscriberInterface
 5 {
 6     public static function getSubscribedEvents()
 7     {
 8         return array(
 9             'kernel.response' => array(
10                 array('onKernelResponsePre', 10),
11                 array('onKernelResponsePost', 0),
12             ),
13             'store.order' => array('onStoreOrder', 0),
14         );
15     }
16 
17     public function onKernelResponsePre(FilterResponseEvent $event)
18     {
19         // ...
20     }
21 
22     public function onKernelResponsePost(FilterResponseEvent $event)
23     {
24         // ...
25     }
26 
27     public function onStoreOrder(FilterOrderEvent $event)
28     {
29         // ...
30     }
31 }
复制代码

 

10.还有一种事件分配器叫作不变事件分配器(Immutable Event Dispatcher):
它是一个固定的事件分配器。它不能注册新的监听者或者订阅者。它使用其它事件分配器注册的监听者或者订阅者。从这个角度说它只是一个原有事件
分配器的代理。
要使用它,首先须要建立一个标准的事件分配器(EventDispatcher 或者 ContainerAwareEventDispatcher)并为其注册一些监听者或者事件订阅者。

复制代码
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();
$dispatcher->addListener('foo.action', function ($event) {
    // ...
});

// ...
复制代码

而后将这个标准的事件分配器注入到一个ImmutableEventDispatcher中:

use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
// ...

$immutableDispatcher = new ImmutableEventDispatcher($dispatcher);

那么从如今开始你就须要使用这个新的事件分配器了。

使用该代理事件分配器的好处是,若是你视图执行一个方法来修改该dispatcher(好比使用其addListener方法)将会收到一个 BadMethodCallException异常被抛出。

 

11.最后咱们看一下通用的事件对象(Event Object)
在咱们调用dispatcher的dispatch方法时若是不给其传入一个自定义的Event对象,那么Dispatcher会自动建立一个默认的Event对象。 这类的Event基类是由Event Dispatcher组件提供,是特地按照面向对象方式设计的API特定对象。它为复杂的应用程序提供了更加优雅可读性更强的代码。

而GenericEvent是一个方便用于那些但愿在整个应用程序中都只使用一个事件对象的状况。它适合于大多数开箱即用的目标,由于它遵循了观察者模式,这种模式下事件对象封装了一个事件主题"subject",以及一些额外的可选扩展参数。

GenericEvent除了其基类Event外还拥有一个简洁的API:
__construct() 构造器能够接收事件主题和任何参数
getSubject() 获取主题
setArgument() 经过键设置一个参数
setArguments() 设置一个参数数组
getArgument() 经过键获取一个参数值
getArguments() 获取全部参数值
hasArgument() 若是某个键值存在,则返回true。

GenericEvent同时还在参数集上实现了ArrayAccess,因此能够很是方便的经过传入额外的参数。
下面是示例假设事件监听者已经被添加到dispatcher。

复制代码
 1 use Symfony\Component\EventDispatcher\GenericEvent;
 2 
 3 $event = new GenericEvent($subject);
 4 $dispatcher->dispatch('foo', $event);
 5 
 6 class FooListener
 7 {
 8     public function handler(GenericEvent $event)
 9     {
10         if ($event->getSubject() instanceof Foo) {
11             // ...
12         }
13     }
14 }
复制代码

经过ArrayAccess的API传入和处理事件参数:

复制代码
 1 use Symfony\Component\EventDispatcher\GenericEvent;
 2 
 3 $event = new GenericEvent(
 4     $subject,
 5     array('type' => 'foo', 'counter' => 0)
 6 );
 7 $dispatcher->dispatch('foo', $event);
 8 
 9 echo $event['counter'];
10 
11 class FooListener
12 {
13     public function handler(GenericEvent $event)
14     {
15         if (isset($event['type']) && $event['type'] === 'foo') {
16             // ... do something
17         }
18 
19         $event['counter']++;
20     }
21 }
复制代码

过滤数据:

复制代码
 1 use Symfony\Component\EventDispatcher\GenericEvent;
 2 
 3 $event = new GenericEvent($subject, array('data' => 'foo'));
 4 $dispatcher->dispatch('foo', $event);
 5 
 6 echo $event['data'];
 7 
 8 class FooListener
 9 {
10     public function filter(GenericEvent $event)
11     {
12         strtolower($event['data']);
13      }
14 }
复制代码

咱们能够在不少地方来直接使用这个GenericEvent对象。

 

原文连接:http://symfony.com/doc/current/components/event_dispatcher/introduction.html

相关文章
相关标签/搜索