EventDispatcher 组件
EventDispatcher 组件提供的工具允许您的应用程序组件通过调度和监听事件来相互通信。
简介
面向对象的代码在确保代码可扩展性方面已经走了很长一段路。通过创建具有明确职责的类,您的代码变得更加灵活,开发人员可以使用子类扩展它们以修改其行为。但是,如果他们想与其他也创建了自己的子类的开发人员共享更改,代码继承不再是答案。
考虑一个实际的例子,您想为您的项目提供一个插件系统。插件应该能够在不干扰其他插件的情况下添加方法,或者在方法执行之前或之后执行某些操作。这对于单继承来说不是一个容易解决的问题,即使 PHP 中允许多重继承,它也有其自身的缺点。
Symfony EventDispatcher 组件实现了 调停者 和 观察者 设计模式,使所有这些成为可能,并使您的项目真正可扩展。
以 HttpKernel 组件 为例。一旦创建了 Response
对象,允许系统中的其他元素在实际使用之前修改它(例如,添加一些缓存头)可能很有用。为了使这成为可能,Symfony 内核调度了一个事件 - kernel.response
。以下是它的工作原理
- 一个监听器(PHP 对象)告诉一个中央调度器对象,它想监听
kernel.response
事件; - 在某个时刻,Symfony 内核告诉调度器对象调度
kernel.response
事件,并传递一个Event
对象,该对象可以访问Response
对象; - 调度器通知(即调用方法)所有
kernel.response
事件的监听器,允许他们每个人修改Response
对象。
安装
1
$ composer require symfony/event-dispatcher
注意
如果您在 Symfony 应用程序之外安装此组件,则必须在您的代码中 require vendor/autoload.php
文件,以启用 Composer 提供的类自动加载机制。阅读 本文 以获取更多详细信息。
用法
另请参阅
本文介绍了如何在任何 PHP 应用程序中将 EventDispatcher 功能用作独立组件。阅读 事件和事件监听器 文章,了解如何在 Symfony 应用程序中使用它。
事件
当事件被调度时,它由一个唯一的名称(例如 kernel.response
)标识,任何数量的监听器可能正在监听该名称。还会创建一个 Event 实例,并传递给所有监听器。正如您稍后将看到的,Event
对象本身通常包含有关正在调度的事件的数据。
事件名称和事件对象
当调度器通知监听器时,它会将实际的 Event
对象传递给这些监听器。基本的 Event
类包含一个用于停止 事件传播 的方法,但没有其他更多内容。
另请参阅
阅读“通用事件对象”以获取有关此基本事件对象的更多信息。
通常,关于特定事件的数据需要与 Event
对象一起传递,以便监听器拥有所需的信息。在这种情况下,当调度事件时,可以传递一个特殊的子类,该子类具有用于检索和覆盖信息的附加方法。例如,kernel.response
事件使用 ResponseEvent,其中包含获取甚至替换 Response
对象的方法。
调度器
调度器是事件调度器系统的中心对象。通常,会创建一个单一的调度器,该调度器维护监听器注册表。当通过调度器调度事件时,它会通知注册到该事件的所有监听器
1 2 3
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
连接监听器
要利用现有事件,您需要将监听器连接到调度器,以便在调度事件时可以通知它。调用调度器的 addListener()
方法将任何有效的 PHP 可调用对象与事件关联起来
1 2
$listener = new AcmeListener();
$dispatcher->addListener('acme.foo.action', [$listener, 'onFooAction']);
addListener()
方法最多接受三个参数
- 此监听器要监听的事件名称(字符串);
- 调度指定事件时将执行的 PHP 可调用对象;
- 可选的优先级,定义为正整数或负整数(默认为
0
)。数字越高,监听器被调用的越早。如果两个监听器具有相同的优先级,则它们按照添加到调度器的顺序执行。
注意
PHP 可调用对象 是一个 PHP 变量,可以被 call_user_func()
函数使用,并且当传递给 is_callable()
函数时返回 true
。它可以是 \Closure
实例,一个实现 __invoke()
方法的对象(实际上就是闭包),一个表示函数的字符串或一个表示对象方法或类方法的数组。
到目前为止,您已经了解了如何将 PHP 对象注册为监听器。您还可以将 PHP 闭包 注册为事件监听器
1 2 3 4 5
use Symfony\Contracts\EventDispatcher\Event;
$dispatcher->addListener('acme.foo.action', function (Event $event): void {
// will be executed when the acme.foo.action event is dispatched
});
一旦监听器在调度器中注册,它将等待直到事件被通知。在上面的示例中,当 acme.foo.action
事件被调度时,调度器调用 AcmeListener::onFooAction()
方法并将 Event
对象作为单个参数传递
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Contracts\EventDispatcher\Event;
class AcmeListener
{
// ...
public function onFooAction(Event $event): void
{
// ... do something
}
}
$event
参数是在调度事件时传递的事件对象。在许多情况下,会传递带有额外信息的特殊事件子类。您可以查看每个事件的文档或实现,以确定传递了哪个实例。
创建和调度事件
除了注册监听器以监听现有事件之外,您还可以创建和调度您自己的事件。当创建第三方库以及您想要保持自己的系统不同组件的灵活性和解耦性时,这非常有用。
创建事件类
假设您想要创建一个新事件,该事件在每次客户使用您的应用程序订购产品时都会被调度。当调度此事件时,您将传递一个自定义事件实例,该实例可以访问已下的订单。首先创建此自定义事件类并记录它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
namespace Acme\Store\Event;
use Acme\Store\Order;
use Symfony\Contracts\EventDispatcher\Event;
/**
* This event is dispatched each time an order
* is placed in the system.
*/
final class OrderPlacedEvent extends Event
{
public function __construct(private Order $order) {}
public function getOrder(): Order
{
return $this->order;
}
}
现在,每个监听器都可以通过 getOrder()
方法访问订单。
调度事件
dispatch() 方法通知给定事件的所有监听器。它接受两个参数:要传递给该事件的每个监听器的 Event
实例和要调度的事件的名称
1 2 3 4 5 6 7 8 9 10
use Acme\Store\Event\OrderPlacedEvent;
use Acme\Store\Order;
// the order is somehow created or retrieved
$order = new Order();
// ...
// creates the OrderPlacedEvent and dispatches it
$event = new OrderPlacedEvent($order);
$dispatcher->dispatch($event);
请注意,特殊的 OrderPlacedEvent
对象被创建并传递给 dispatch()
方法。现在,任何 OrderPlacedEvent::class
事件的监听器都将接收 OrderPlacedEvent
。
注意
如果您不需要将任何额外数据传递给事件监听器,您也可以使用默认的 Event 类。在这种情况下,您可以在通用的 StoreEvents
类中记录事件及其名称,类似于 KernelEvents 类
1 2 3 4 5 6 7 8 9
namespace App\Event;
class StoreEvents {
/**
* @Event("Symfony\Contracts\EventDispatcher\Event")
*/
public const ORDER_PLACED = 'order.placed';
}
并使用 Event 类来调度事件
1 2 3
use Symfony\Contracts\EventDispatcher\Event;
$this->eventDispatcher->dispatch(new Event(), StoreEvents::ORDER_PLACED);
使用事件订阅器
监听事件最常见的方法是向调度器注册事件监听器。此监听器可以监听一个或多个事件,并在每次调度这些事件时收到通知。
监听事件的另一种方法是通过事件订阅器。事件订阅器是一个 PHP 类,它能够准确地告诉调度器它应该订阅哪些事件。它实现了 EventSubscriberInterface 接口,该接口需要一个名为 getSubscribedEvents() 的静态方法。以下是一个订阅器示例,它订阅了 kernel.response
和 OrderPlacedEvent::class
事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
namespace Acme\Store\Event;
use Acme\Store\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class StoreSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => [
['onKernelResponsePre', 10],
['onKernelResponsePost', -10],
],
OrderPlacedEvent::class => 'onPlacedOrder',
];
}
public function onKernelResponsePre(ResponseEvent $event): void
{
// ...
}
public function onKernelResponsePost(ResponseEvent $event): void
{
// ...
}
public function onPlacedOrder(OrderPlacedEvent $event): void
{
$order = $event->getOrder();
// ...
}
}
这与监听器类非常相似,只是该类本身可以告诉调度器它应该监听哪些事件。要向调度器注册订阅器,请使用 addSubscriber() 方法
1 2 3 4 5
use Acme\Store\Event\StoreSubscriber;
// ...
$subscriber = new StoreSubscriber();
$dispatcher->addSubscriber($subscriber);
调度器将自动为 getSubscribedEvents()
方法返回的每个事件注册订阅器。此方法返回一个数组,该数组以事件名称作为索引,其值是要调用的方法名称或由要调用的方法名称和优先级(默认为 0
的正整数或负整数)组成的数组。
上面的示例展示了如何在订阅器中为同一事件注册多个监听器方法,并展示了如何传递每个监听器方法的优先级。数字越高,方法被调用的越早。在上面的示例中,当 kernel.response
事件被触发时,方法 onKernelResponsePre()
和 onKernelResponsePost()
将按该顺序被调用。
停止事件流/传播
在某些情况下,监听器阻止调用任何其他监听器可能是有意义的。换句话说,监听器需要能够告诉调度器停止事件向未来监听器的所有传播(即不再通知任何监听器)。这可以从监听器内部通过 stopPropagation() 方法完成
1 2 3 4 5 6 7 8
use Acme\Store\Event\OrderPlacedEvent;
public function onPlacedOrder(OrderPlacedEvent $event): void
{
// ...
$event->stopPropagation();
}
现在,尚未被调用的 OrderPlacedEvent::class
的任何监听器都将不会被调用。
可以使用 isPropagationStopped() 方法检测事件是否已停止,该方法返回一个布尔值
1 2 3 4 5
// ...
$dispatcher->dispatch($event, 'foo.event');
if ($event->isPropagationStopped()) {
// ...
}
EventDispatcher 感知的事件和监听器
EventDispatcher
始终将调度的事件、事件的名称和对自身的引用传递给监听器。这可能会导致 EventDispatcher
的一些高级应用,包括在监听器内部调度其他事件、链接事件甚至将监听器延迟加载到调度器对象中。
事件名称内省
EventDispatcher
实例以及调度的事件的名称作为参数传递给监听器
1 2 3 4 5 6 7 8 9 10
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class MyListener
{
public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher): void
{
// ... do something with the event name
}
}