事件和事件監聽
在symfony程式執行期間,大量的事件通知(event notifications)會被觸發。你的程式可以監聽這些通知,並執行任意程式碼作為回應。
Symfony本身提供的內部事件,被定義在KernelEvents
類別中。第三方Bundle和類別庫也會觸發大量事件,你自己的程式可以觸發自訂事件。
本文所展示的所有例子,考慮到一致性,使用了相同的KernelEvents::EXCEPTION事件。在你自己的程式中,你可以使用任何事件,甚至在同一訂閱器中(subscriber)混合若干事件。
建立一個事件監聽 ¶
監聽一個事件最常用的方式是註冊一個事件監聽(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 // 你可以从接收到的事件中,取得异常对象 $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 // 自定义响应对象,来显示异常的细节 $response = new Response(); $response->setContent($message); // HttpExceptionInterface is a special type of exception that // holds status code and header details // HttpExceptionInterface是一个特殊类型的异常,持有状态码和头信息的细节 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 // 发送修改后的响应对象到事件中 $event->setResponse($response); }}
每一個事件,都要接收「類型略有不同」的$event
物件。對於kernel.exception
事件,這個物件是GetResponseForExceptionEvent。要了解每一個「事件監聽」所接收到的「事件物件」之類型,參考KernelEvents,或是你要監聽的特定事件之文檔。
現在,類別被創建了,你只需把它註冊成服務,然後透過使用一個特殊的「tag」(標籤),告訴Symfony這是一個針對kernel.exception
事件的「監聽」:
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'));
有一個可選的tag屬性是method
#,它定義了「當事件被觸發時,哪個方法要被執行」。預設時,方法的名字是on
“駝峰事件名稱”。如果事件是kernel.exception
的話,預設執行的方法則是onKernelException()
。
另有一個可選的tag屬性是priority
,它的預設值是0
,用來控制監聽被執行的順序(一個監聽器的優先順序愈高則愈早被執行)。這在你想要「確保某個監聽在其他監聽之前被執行」時是有用的。 Symfony的內部監聽,其優先範圍是-255
到255
,但你自己的監聽可以使用任何正或負的整數。
建立一個事件訂閱 ¶
另一種監聽事件的方式是event subscriber事件訂閱,它是一個類,定義了一或多個方法,用於監聽一或多個事件。同事件監聽的主要區別在於,訂閱器始終知道它們正在監聽的事件是哪一個。
在一個給定的訂閱器中,不同的方法可以監聽同一個事件。方法被執行時的順序,透過每一個方法中的priority
參數來定義(優先順序愈高則方法越早被呼叫)。要了解更多關於訂閱器的內容,請參考EventDispatcher元件。
下例展示了一個事件訂閱,定義了若干方法,監聽的是同一個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) { // ... }}
現在,你只需把這個類註冊成服務,並打上kernel.event_subscriber
標籤,即可告訴Symofny這是一個事件訂閱器:
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事件,檢查Type ¶
一個單一頁面,可以產生若干次請求(一個主請求[master request],然後是多個子請求[sub-requests],典型的像是如何在模板中嵌入控制器)。對於Symfony核心事件,你可能需要檢查一下,看看這個事件是一個「主」請求還是一個「子」請求:
// 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 // 如果不是主请求,就什么也不做 return; } // ... }}
特定行為,像是對真正的請求進行檢查這種,可能並不需要在子請求的監聽中進行。
監聽還是訂閱 ¶
監聽器和訂閱器,在同一程式中使用時,可能界限模糊。決定使用哪一種,通常由個人口味決定。但是,每種都有各自的優點:
#訂閱器易於重複使用,因為與事件有關的內容存在於類別中,而不是存在於服務定義中。這導致Symfony內部使用訂閱器;
監聽器更靈活,因為bundles可以有條件地開啟或關閉它們,基於設定檔中的某些“選項值」。
對事件監聽器進行調試 ¶
#使用命令列,你可以找到「哪些監聽被註冊到事件派遣器」。要顯示全部事件及其監聽,執行:
# 1 | $ php bin/console debug:event-dispatcher |
#透過指定事件名稱,你可以得到針對此特定事件進行註冊的監聽:
1 | $ php bin/console debug:event-dispatcher kernel.exception |