9.12. 事件分发器(Event Dispatcher)——为特定事件编写自定义代码
事件系统允许你构建更复杂的系统,通过在特定事件上运行自定义代码来改变功能。许多来自 Drupal 7 的钩子(hooks)已经被事件(events)所取代。这使得 Drupal 核心和许多扩展模块的工作方式得到了统一。事件系统本身来自 Symfony,由以下几个部分组成:
事件订阅者(Event Subscribers) — 即“事件监听者”,在特定事件发生时执行的函数或方法。在代码中,它是一个实现以下接口的类:
\Symfony\Component\EventDispatcher\EventSubscriberInterface
事件注册器(Event Registry) — 负责收集和按照触发顺序组织所有事件订阅者。注册器存储在事件分发器(Event Dispatcher)对象中,作为一个事件名称和优先级的键值对数组。当事件作为服务注册时,它会成为一个全局可用的分发器。
事件分发器(Event Dispatcher) — 事件触发的机制,它负责在正确的时间调用对应的事件订阅者。通常至少有一个 Event Dispatcher 实例被注册为服务。该类实现以下接口:
\Symfony\Component\EventDispatcher\EventDispatcherInterface
事件上下文(Event Context) — 许多事件需要特定的数据集,这些数据可能对事件订阅者至关重要。这个数据可能是传递给订阅者的简单值,也可能是包含详细数据的类。事件上下文类继承自:
\Symfony\Component\EventDispatcher\Event
让我们通过一个示例来了解事件系统的工作原理。
我已将全部代码上传到 GitHub 的 drupalbook_examples 模块中,你可以下载并添加到你的网站中:
https://github.com/levmyshkin/drupalbook8
在 Drupal 8 中,hook_init()
已经被移除:
https://www.drupal.org/node/2013014
现在可以通过事件订阅者(Event Subscriber)在页面加载时执行所需的代码:
modules/custom/drupalbook_examples/drupalbook_examples.services.yml
services:
drupalbook_examples.event_subscriber:
class: Drupal\drupalbook_examples\EventSubscriber\DrupalbookExamplesSubscriber
tags:
- {name: event_subscriber}
为了注册事件订阅者,需要在模块的 *.services.yml
文件中声明服务。在此文件中定义事件订阅者类,并通过 tags
添加 event_subscriber
标记。接下来,我们需要创建事件订阅者类:
modules/custom/drupalbook_examples/src/EventSubscriber/DrupalbookExamplesSubscriber.php
<?php
namespace Drupal\drupalbook_examples\EventSubscriber;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DrupalbookExamplesSubscriber implements EventSubscriberInterface {
public function checkForRedirection(GetResponseEvent $event) {
if ($event->getRequest()->query->get('redirect-me')) {
$event->setResponse(new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE])));
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('checkForRedirection');
return $events;
}
}
来看一下这段代码的关键点。我们的事件订阅者类实现了 EventSubscriberInterface
接口,该接口的主要方法是 getSubscribedEvents()
,它定义了订阅者要监听哪些事件,以及在事件发生时调用哪个方法。在本例中,我们监听 KernelEvents::REQUEST
事件(即 Drupal 处理请求的最初阶段)。当事件触发时,将调用 checkForRedirection()
方法。在方法中,如果检测到请求参数中包含 redirect-me
,则将用户重定向到首页。
此代码会在几乎每次页面加载时运行。但请注意,如果页面已被缓存,Drupal 可能会直接从缓存中返回响应,而不会执行完整的事件流。
在这种情况下,可以使用类似 hook_boot()
的机制(它也在 Drupal 8 中被移除):
https://www.drupal.org/node/1909596
我们可以添加另一个方法 redirectBeforeWithoutCache()
:
modules/custom/drupalbook_examples/src/EventSubscriber/DrupalbookExamplesSubscriber.php
<?php
namespace Drupal\drupalbook_examples\EventSubscriber;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DrupalbookExamplesSubscriber implements EventSubscriberInterface {
public function checkForRedirection(GetResponseEvent $event) {
if ($event->getRequest()->query->get('redirect-me')) {
$event->setResponse(new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE])));
}
}
public function redirectBeforeWithoutCache(GetResponseEvent $event) {
if ($event->getRequest()->query->get('redirect-me')) {
$event->setResponse(new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE])));
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('checkForRedirection');
$events[KernelEvents::REQUEST][] = array('redirectBeforeWithoutCache', 300);
return $events;
}
}
在 getSubscribedEvents()
方法中,我们为同一个事件 KernelEvents::REQUEST
注册了两个方法。第二个方法设置了优先级 300,使其比其他订阅者更早执行。这样,即使页面被缓存,重定向逻辑也能生效。
Drupal 中有很多可以订阅的事件。更多示例可以在官方文档中找到: