事件和事件監聽


在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的內部監聽,其優先範圍是-255255,但你自己的監聽可以使用任何正或負的整數。

#

建立一個事件訂閱 

另一種監聽事件的方式是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