事件和事件监听
在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 |