一個插件系統中,A插件在不影響其它插件的前提下,添加新的方法,或者在一個方法運行前做一些準備工作,通過繼承來實現擴展是很不容易的,由于插件之間的關聯關系,A插件的改變也會使得關聯的插件被動的修改。
Symfony2的EventDispatcher組件實現了中介者(mediator)模式,實現了插件之間的解耦和關聯的關系。
舉個栗子,在HttpKernel組件中,一旦Response被創建了,在Response發送給客戶端的時候,允許系統的其它模塊修改它是很有必要和很有用的(例如:在頭部添加緩存字段)。Symfony2內核調度kernel.response事件,那么監聽該事件的監聽器就可以修改Response對象了:
a) 監聽器(PHP對象)告訴一個特定的dispatcher類,它要監聽kernel.response事件;
b) 在某一時刻,Symfony就會告訴dispatcher調度kernel.response事件,把一個包含Response對象的Event對象傳入事件調度方法EventDispatcher::dispatch()
c) dispatcher通知所有監聽kernel.response事件的監聽器運行,允許這些監聽器修改Response對象。
使用方法
Events
當一個事件被調度,該事件會有一個唯一的名稱(例如:kernel.response),可以被任意多個監聽器監聽。同時實例化一個Event對象,并傳入到所有的監聽器中。在后面會看到,Event對象包含了被調度事件的數據。
命名規范
事件名可以是任意的字符串,但是可以遵循以下幾點命名規范:
* 只使用小寫字符、數字、點、下劃線;
* 前綴名使用帶有點的命名;
* 結束部分的名字使用一個能表達當前事件行為的動詞;
事件名和事件對象
dispatcher調度事件通知監聽器時候,會把一個Event對象作為參數傳入到監聽器中。基類的Event非常簡單,只有一個停止事件傳遞給下一個監聽器的方法,沒有太多別的了。
通常,一個特定事件的數據都會保存到Event對象中,方便把數據傳遞給監聽器。在kernel.response事件中,傳入到監聽器的Event對象是子類FliterResponseEvent的對象,FliterResponseEvent是Event的子類。FliterResponseEvent包含getReponse和setResponse方法,允許監聽器獲得或者修改Response對象。
總的來說:創建一個監聽特定事件的監聽器的時候,Event的一個子類會傳入到監聽器中,監聽器可以通過該子類的方法獲得和修改信息。
事件調度器(dispatcher)
dispatcher是事件調度系統的核心,一般來說,一個dispatcher內包含一個監聽器的列表,當一個事件需要被調度的時候,dispatcher就會通知它包含的所有監聽該事件的監聽器。
1 use Symfony\Component\EventDispatcher\EventDispatcher; 2 3 $dispatcher = new EventDispatcher();
關聯監聽器
把監聽器添加到dispatcher上,監聽特定的事件,那么該事件被調度的時候,dispatcher就會通知監聽器工作了。dispatcher使用addListener方法把一個監聽器(PHP callable)添加到一個事件上。
1 $listener = new AcmeListener(); 2 $dispatcher->addListener('foo.action', array($listener, 'onFooAction'));
addListener方法有三個參數:
* 監聽器需要監聽是的事件的名稱;
* 監聽器(一個PHP callable);
* 一個可選的表示優先級的整數(數值越高優先級越高,監聽器就會更早的被觸發),默認為0,如果優先級一樣,那么誰先添加就先觸發;
PHP callable是指能作為參數傳入call_user_func()或者傳入is_callable()函數執行后返回true的PHP 變量。PHP callable可以是 \Closure實例,一個實現了__invoke方法的對象,或者是表示一個函數的字符串,或者一個表示對象方法或者類方法的數組。到目前為止,我們看過把一個PHP對象作為監聽器,我們也可以把Closure對象作為監聽器。1 use Symfony\Component\EventDispatcher\Event; 2 3 $dispatcher->addListener('foo.action', function (Event $event) { 4 // will be executed when the foo.action event is dispatched 5 });
在上面的例子中,foo.action事件被調度,dispatcher就調用AcmeListener::onFooAction方法,并把Event對象作為唯一的參數傳入方法中。
1 use Symfony\Component\EventDispatcher\Event; 2 3 class AcmeListener 4 { 5 // ... 6 7 public function onFooAction(Event $event) 8 { 9 // ... do something 10 } 11 }
?在實際使用中,都是傳入一個特定的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 }
創建和調度事件
除了系統內置的事件,我們也可以創建和調度自定義的事件。這是很有好處的,當我們使用第三方類庫的時,還有可以使不同的組件之間解耦,使系統更靈活健壯。
靜態的Events類
假如我們要創建一個事件——store.order——當訂單被創建的時候就會被觸發。
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'; }
這個類并沒有什么方法,也不做什么操作,只是定義了事件名稱,方便管理和組織事件。監聽這個事件的監聽器都會被傳入一個FilterOrderEvent對象。
創建一個Event對象
接著,當你調度這個新的事件的時候,會創建一個Event對象傳如到dispatcher的dispatch()方法,dispatcher就把這個Event對象傳給所有的監聽該事件的監聽器。如果我們不需要向監聽器傳入任何信息,那么可以使用系統默認的Symfony\Component\EventDispatcher\Event?類。然而,很多時候,我們都需要傳入特定的信息到監聽器,那么我們可以創建一個類繼承Symfony\Component\EventDispatcher\Event。
例如,我們需要在所有的監聽器中傳入order對象:
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 }
所有監聽器都可以通過FilterOrderEvent的getOrder方法獲得order對象。
調度事件
dispatcher的dispatch()方法通知監聽給定的事件的所有監聽器,有兩個參數,一個是需要調度的事件名,另一個就是傳給所有監聽器的Event對象。
1 use Acme\StoreBundle\StoreEvents; 2 use Acme\StoreBundle\Order; 3 use Acme\StoreBundle\Event\FilterOrderEvent; 4 5 // the order is somehow created or retrieved 6 $order = new Order(); 7 // ... 8 9 // create the FilterOrderEvent and dispatch it 10 $event = new FilterOrderEvent($order); 11 $dispatcher->dispatch(StoreEvents::STORE_ORDER, $event);
FilterOrderEvent對象作為參數傳入到dispatch方法,現在,任何監聽store.order事件的監聽器都會接收到FilterOrderEvent對象,并通過調用getOrder方法獲得order對象。
1 // some listener class that's been registered for "store.order" event 2 use Acme\StoreBundle\Event\FilterOrderEvent; 3 4 public function onStoreOrder(FilterOrderEvent $event) 5 { 6 $order = $event->getOrder(); 7 // do something to or with the order 8 }
Event Subscribers??
最普遍的監聽事件的方法是注冊一個監聽器到dispatcher中,一個監聽器可以監聽一個或者多個事件。
還有另一種監聽事件的方法是使用Event SubScriber,Event SubScriber是一個PHP類,能夠準確的告訴dispatcher它訂閱了那些事件。實現EventSubscriberInterface接口,該接口有一個靜態的方法getSubscriberdEvents。
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){// ... } }
這個監聽器類很簡單,告訴了dispatcher監聽了什么事件,還有監聽的事件觸發的方法。addSubscriber()方法把subscriber注冊到dispatcher。
1 use Acme\StoreBundle\Event\StoreSubscriber; 2 3 $subscriber = new StoreSubscriber(); 4 $dispatcher->addSubscriber($subscriber);
dispatcher準確的把Subscriber注冊到EventSubscriberInterface::getSubscriberdEvents()返回的事件里,EventSubscriberInterface::getSubscriberdEvents()方法返回一個數組,數組的鍵對應Subscriber監聽的事件,值對應這Subscriber處理該事件調用的一個方法或者一組方法。上面的例子中,一組監聽器的方法對應這一個事件,同時我們也可以設置優先級來控制這組方法的執行先后順序。當kernel.response事件被觸發,
onKernelResponsePre
,?onKernelResponseMid
, 和?onKernelResponsePost三個方法就會先后執行。
停止事件的傳遞
在一些情況下,監聽器可以停止事件傳遞下去,防止后續的監聽器被調用,換句話說,監聽器必須通知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 }
?