事件和事件監(jiān)聽(tīng)
在symfony程序執(zhí)行期間,大量的事件通知(event notifications)會(huì)被觸發(fā)。你的程序可以監(jiān)聽(tīng)這些通知,并執(zhí)行任意代碼作為回應(yīng)。
Symfony自身提供的內(nèi)部事件,被定義在KernelEvents
類中。第三方Bundle和類庫(kù)也會(huì)觸發(fā)大量事件,你自己的程序可以觸發(fā)自定義事件。
本文展示的所有例子,考慮到一致性,使用了相同的KernelEvents::EXCEPTION事件。在你自己的程序中,你可以使用任何事件,甚至在同一訂閱器中(subscriber)混合若干事件。
創(chuàng)建一個(gè)事件監(jiān)聽(tīng) ?
監(jiān)聽(tīng)一個(gè)事件最常用的方式是注冊(cè)一個(gè)事件監(jiān)聽(tīng)(event listener):
// src/AppBundle/EventListener/ExceptionListener.phpnamespace AppBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class ExceptionListener{ public function onKernelException(GetResponseForExceptionEvent $event) { // You get the exception object from the received event // 你可以從接收到的事件中,取得異常對(duì)象 $exception = $event->getException(); $message = sprintf( 'My Error says: %s with code: %s', $exception->getMessage(), $exception->getCode() ); // Customize your response object to display the exception details // 自定義響應(yīng)對(duì)象,來(lái)顯示異常的細(xì)節(jié) $response = new Response(); $response->setContent($message); // HttpExceptionInterface is a special type of exception that // holds status code and header details // HttpExceptionInterface是一個(gè)特殊類型的異常,持有狀態(tài)碼和頭信息的細(xì)節(jié) if ($exception instanceof HttpExceptionInterface) { $response->setStatusCode($exception->getStatusCode()); $response->headers->replace($exception->getHeaders()); } else { $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR); } // Send the modified response object to the event // 發(fā)送修改后的響應(yīng)對(duì)象到事件中 $event->setResponse($response); }}
每一個(gè)事件,都要接收“類型略有不同”的$event
對(duì)象。對(duì)于kernel.exception
事件,這個(gè)對(duì)象是GetResponseForExceptionEvent。要了解每一個(gè)“事件監(jiān)聽(tīng)”所接收到的“事件對(duì)象”之類型,參考KernelEvents,或是你要監(jiān)聽(tīng)的特定事件之文檔。
現(xiàn)在,類被創(chuàng)建了,你只需把它注冊(cè)成服務(wù),然后通過(guò)使用一個(gè)特殊的“tag”(標(biāo)簽),告訴Symfony這是一個(gè)針對(duì)kernel.exception
事件的“監(jiān)聽(tīng)”:
YAML:# app/config/services.ymlservices: app.exception_listener: class: AppBundle\EventListener\ExceptionListener tags: - { name: kernel.event_listener, event: kernel.exception }
xml:<!-- app/config/services.xml --><?xml version="1.0" encoding="UTF-8" ?><container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="app.exception_listener" class="AppBundle\EventListener\ExceptionListener"> <tag name="kernel.event_listener" event="kernel.exception" /> </service> </services></container>
php:// app/config/services.php$container ->register('app.exception_listener', 'AppBundle\EventListener\ExceptionListener') ->addTag('kernel.event_listener', array('event' => 'kernel.exception'));
有一個(gè)可選的tag屬性是method
,它定義了“當(dāng)事件被觸發(fā)時(shí),哪個(gè)方法要被執(zhí)行”。默認(rèn)時(shí),方法的名字是on
+“駝峰事件名”。如果事件是kernel.exception
的話,默認(rèn)執(zhí)行的方法則是onKernelException()
。
另有一個(gè)可選的tag屬性是priority
,它的默認(rèn)值是0
,用來(lái)控制監(jiān)聽(tīng)被執(zhí)行的順序(一個(gè)監(jiān)聽(tīng)器的優(yōu)先級(jí)愈高則愈早被執(zhí)行)。這在你要“確保某個(gè)監(jiān)聽(tīng)在其他監(jiān)聽(tīng)之前被執(zhí)行”時(shí)是有用的。Symfony的內(nèi)部監(jiān)聽(tīng),其優(yōu)先級(jí)范圍是-255
到255
,但你自己的監(jiān)聽(tīng)可以使用任何正或負(fù)的整數(shù)。
創(chuàng)建一個(gè)事件訂閱 ?
另一種監(jiān)聽(tīng)事件的方式是event subscriber事件訂閱,它是一個(gè)類,定義了一或多個(gè)方法,用于監(jiān)聽(tīng)一或多個(gè)事件。同事件監(jiān)聽(tīng)的主要區(qū)別在于,訂閱器始終知道它們正在監(jiān)聽(tīng)的事件是哪一個(gè)。
在一個(gè)給定的訂閱器中,不同的方法可以監(jiān)聽(tīng)同一個(gè)事件。方法被執(zhí)行時(shí)的順序,通過(guò)每一個(gè)方法中的priority
參數(shù)來(lái)定義(優(yōu)先級(jí)愈高則方法愈早被調(diào)用)。要了解更多關(guān)于訂閱器的內(nèi)容,參考EventDispatcher組件。
下例展示了一個(gè)事件訂閱,定義了若干方法,監(jiān)聽(tīng)的是同一個(gè)kernel.exception
事件:
// src/AppBundle/EventSubscriber/ExceptionSubscriber.phpnamespace AppBundle\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;use Symfony\Component\HttpKernel\KernelEvents; class ExceptionSubscriber implements EventSubscriberInterface{ public static function getSubscribedEvents() { // return the subscribed events, their methods and priorities // 返回被訂閱的事件,以及它們的方法和屬性 return array( KernelEvents::EXCEPTION => array( array('processException', 10), array('logException', 0), array('notifyException', -10), ) ); } public function processException(GetResponseForExceptionEvent $event) { // ... } public function logException(GetResponseForExceptionEvent $event) { // ... } public function notifyException(GetResponseForExceptionEvent $event) { // ... }}
現(xiàn)在,你只需把這個(gè)類注冊(cè)成服務(wù),并打上kernel.event_subscriber
標(biāo)簽,即可告訴Symofny這是一個(gè)事件訂閱器:
PHP:// app/config/services.php$container ->register( 'app.exception_subscriber', 'AppBundle\EventSubscriber\ExceptionSubscriber' ) ->addTag('kernel.event_subscriber');
XML:<!-- app/config/services.xml --><?xml version="1.0" encoding="UTF-8" ?><container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="app.exception_subscriber" class="AppBundle\EventSubscriber\ExceptionSubscriber"> <tag name="kernel.event_subscriber"/> </service> </services></container>
YAML:# app/config/services.ymlservices: app.exception_subscriber: class: AppBundle\EventSubscriber\ExceptionSubscriber tags: - { name: kernel.event_subscriber }
Request事件,檢查T(mén)ype ?
一個(gè)單一頁(yè)面,可以產(chǎn)生若干次請(qǐng)求(一個(gè)主請(qǐng)求[master request],然后是多個(gè)子請(qǐng)求[sub-requests],典型的像是如何在模板中嵌入控制器)。對(duì)于Symfony核心事件,你可能需要檢查一下,看這個(gè)事件是一個(gè)“主”請(qǐng)求還是一個(gè)“子”請(qǐng)求:
// src/AppBundle/EventListener/RequestListener.phpnamespace AppBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent;use Symfony\Component\HttpKernel\HttpKernel;use Symfony\Component\HttpKernel\HttpKernelInterface; class RequestListener{ public function onKernelRequest(GetResponseEvent $event) { if (!$event->isMasterRequest()) { // don't do anything if it's not the master request // 如果不是主請(qǐng)求,就什么也不做 return; } // ... }}
特定行為,像是對(duì)真正的請(qǐng)求進(jìn)行檢查這種,可能并不需要在子請(qǐng)求的監(jiān)聽(tīng)中進(jìn)行。
監(jiān)聽(tīng)還是訂閱 ?
監(jiān)聽(tīng)器和訂閱器,在同一程序中使用時(shí),可能界限模糊。決定使用哪一種,通常由個(gè)人口味決定。但是,每種都有各自的優(yōu)點(diǎn):
訂閱器易于復(fù)用,因?yàn)榕c事件有關(guān)的內(nèi)容存在于類中,而不是存在于服務(wù)定義中。這導(dǎo)致Symfony內(nèi)部使用訂閱器;
監(jiān)聽(tīng)器更靈活,因?yàn)閎undles可以有條件地開(kāi)啟或關(guān)閉它們,基于配置文件中的某些“選項(xiàng)值”。
對(duì)事件監(jiān)聽(tīng)器進(jìn)行調(diào)試 ?
使用命令行,你可以找到“哪些監(jiān)聽(tīng)被注冊(cè)到事件派遣器”。要顯示全部事件及其監(jiān)聽(tīng),運(yùn)行:
1 | $ php bin/console debug:event-dispatcher |
通過(guò)指定事件名稱,你可以得到針對(duì)此特定事件進(jìn)行注冊(cè)的監(jiān)聽(tīng):
1 | $ php bin/console debug:event-dispatcher kernel.exception |