logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

9.12. 事件分发器(Event Dispatcher)——为特定事件编写自定义代码

16/10/2025, by Ivan

事件系统允许你构建更复杂的系统,通过在特定事件上运行自定义代码来改变功能。许多来自 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 中有很多可以订阅的事件。更多示例可以在官方文档中找到:

https://www.drupal.org/docs/8/creating-custom-modules/event-systems-overview-how-to-subscribe-to-and-dispatch-events