olvlvl / event-dispatcher
PSR-14 事件调度器实现
Requires
- php: >=7.2
- psr/event-dispatcher: ^1.0
Requires (Dev)
- phpstan/phpstan: ^0.12.91
- phpunit/phpunit: ^8.5
- psr/container: ^1.0
- symfony/config: ^5.3
- symfony/dependency-injection: ^5.3
- symfony/yaml: ^5.3
Provides
README
olvlvl/event-dispatcher
提供了psr/event-dispatcher的实现,该实现建立了一个基于事件的扩展和协作的通用机制。
包亮点
- 支持事件继承,包括接口。
- 支持可停止的事件。
- 提供一系列可组合的事件调度器和监听器提供者。
- 引入可变监听器提供者。
- 提供symfony/dependency-injection的编译器遍历,带有优先级和相关性。
安装
composer require olvlvl/event-dispatcher
事件调度器
事件调度器是一个服务对象,由发射器提供事件对象。调度器负责确保事件传递给所有相关的监听器,但必须将确定负责监听器的任务推迟到监听器提供者。
基本事件调度器
BasicEventDispatcher
是事件调度器的一个基本实现,符合调度器的要求。
<?php use olvlvl\EventDispatcher\BasicEventDispatcher; /* @var Psr\EventDispatcher\ListenerProviderInterface $listenerProvider */ /* @var object $event */ $dispatcher = new BasicEventDispatcher($listenerProvider); $dispatcher->dispatch($event);
缓冲事件调度器
在某些情况下,可能希望延迟事件的调度。例如,一个提供API以创建菜谱的应用程序,需要索引创建的菜谱并运行额外的耗时计算,将希望延迟事件调度,以便尽快响应用户。
BufferedEventDispatcher
装饰了事件调度器并缓冲可以在以后调度的事件。用户可以提供区分器,以决定事件是否应该被缓冲或立即调度。
注意:使用此类调度器时要小心!因为事件调度被延迟,这将对期望事件被修改的用户造成问题。
注意:根据调度器的需求,已停止的事件将被丢弃且不会被缓冲。
<?php use olvlvl\EventDispatcher\BufferedEventDispatcher; /* @var Psr\EventDispatcher\EventDispatcherInterface $decorated */ /* @var object $eventA */ /* @var SampleInterface $eventB */ /* @var object $eventC */ $dispatcher = new BufferedEventDispatcher( $decorated, // An optional discriminator function (object $event): bool { return !$event instanceof SampleInterface; } ); $dispatcher->dispatch($eventA); // $eventB is dispatched immediately $dispatcher->dispatch($eventB); $dispatcher->dispatch($eventC); // ... Some code here, maybe reply to a request. $dispatchedEvents = $dispatcher->dispatchBufferedEvents();
监听器提供者
监听器提供者负责确定哪些监听器与特定事件相关,并应该对给定事件进行调用。olvlvl/event-dispatcher
提供了一些监听器提供者实现,这些实现符合监听器提供者的要求和推荐。
带有映射的监听器提供者
ListenerProviderWithMap
是一个使用事件/监听器对数组的监听器提供者。
<?php use olvlvl\EventDispatcher\ListenerProviderWithMap; /* @var callable $callableA */ /* @var callable $callableB */ /* @var callable $callableC */ $provider = new ListenerProviderWithMap([ MyEventA::class => [ $callableA ], MyEventInterfaceA::class => [ $callableB, $callableC ], ]);
带有容器的监听器提供者
ListenerProviderWithContainer
是一个使用事件/服务id对数组的监听器提供者,并从PSR容器中检索监听器。
注意:olvlvl/event-dispatcher
为symfony/dependency-injection提供了编译器遍历,这对于收集事件监听器和构建监听器提供者非常有用。
<?php use olvlvl\EventDispatcher\ListenerProviderWithContainer; /* @var Psr\Container\ContainerInterface $container */ $provider = new ListenerProviderWithContainer([ SampleEventA::class => [ 'serviceA' ], SampleEventInterfaceA::class => [ 'serviceA', 'serviceB' ], ], $container);
可变监听器提供者
MutableListenerProvider
是一个可变的监听器提供者,即可以添加和删除监听器。为此,提供者没有构造函数参数,这样它所包含的任何监听器也可以被删除。
监听器提供者实现了MutableListenerProviderInterface
接口,该接口扩展了ListenerProviderInterface
接口。该接口可以用来区分可变监听器提供者和不可变监听器提供者。
<?php use olvlvl\EventDispatcher\MutableListenerProviderInterface; /* @var Psr\EventDispatcher\ListenerProviderInterface $provider */ if ($provider instanceof MutableListenerProviderInterface) { // ... we can add or remove Listeners. }
可以使用appendListenerForEvent()
方法将事件监听器添加到列表的末尾,或者使用prependListenerForEvent()
方法将其添加到列表的开头。这两个方法都返回一个可调用的对象,可以用来移除监听器。
注意:如果为事件类型添加了两次监听器,将抛出LogicException
异常。调用不会静默失败,因为监听器的预处理或后置可能会产生非常不同且不可预测的结果。
以下示例演示了如何将监听器追加到可变监听器提供者的事件中。在这个例子中,监听器使用“remove”可调用对象在调用后删除自己。这就是设置“一次性”监听器的方式。当然,这只是一个应用示例。
<?php use olvlvl\EventDispatcher\MutableListenerProvider; $provider = new MutableListenerProvider(); $remove = $provider->appendListenerForEvent( SampleEvent::class, function (SampleEvent $event) use (&$remove): void { // This is how one can implement a "once" listener. // The listener is removed when it's called. $remove(); // ... do something with the event here. } );
监听器提供者链
使用ListenerProviderChain
,可以将多个监听器提供者组合起来,使其像一个整体一样工作。它们将按顺序调用以提供事件的监听器。
该链是可变的,可以使用appendListenerProviders()
方法将监听器提供者添加到链的末尾,或使用prependListenerProviders()
方法将其添加到链的开头。
注意:由于ListenerProviderChain
与其他提供者监听器一样,因此创建链的链是可能的。
以下示例演示了如何创建监听器提供者链,并通过追加和前置其他提供者来修改该链。
<?php use olvlvl\EventDispatcher\ListenerProviderChain; // Compose a Listener Provider from a number of Listener Providers. /* @var $providerA olvlvl\EventDispatcher\MutableListenerProvider */ /* @var $providerB olvlvl\EventDispatcher\ListenerProviderWithMap */ /* @var $providerC olvlvl\EventDispatcher\ListenerProviderWithContainer */ $provider = new ListenerProviderChain([ $providerA, $providerB, $providerC ]); // Listener Providers can be added to the end of the chain. /* @var $providerD Psr\EventDispatcher\ListenerProviderInterface */ /* @var $providerE Psr\EventDispatcher\ListenerProviderInterface */ $provider->appendListenerProviders($providerD, $providerE); // Listener Providers can be added to the beginning of the chain. /* @var $providerF Psr\EventDispatcher\ListenerProviderInterface */ /* @var $providerG Psr\EventDispatcher\ListenerProviderInterface */ $provider->prependListenerProviders($providerF, $providerG); // Obtain the Listeners for an event /* @var object $event */ foreach ($provider->getListenersForEvent($event) as $listener) { // ... do something with the listeners }
监听器提供者过滤器
ListenerProviderFilter
装饰了一个监听器提供者,以根据用户指定的判别器过滤监听器。过滤器可以用来实现某种形式的访问控制,以便某些监听器只有在当前用户具有某种权限时才会被调用。
以下示例演示了如何使用过滤器丢弃SampleEventA
的$listener_1
和SampleEventC
的$listener_2
。
<?php use olvlvl\EventDispatcher\ListenerProviderFilter; use olvlvl\EventDispatcher\ListenerProviderWithMap; /* @var callable $listener_1 */ /* @var callable $listener_2 */ $provider = new ListenerProviderFilter( new ListenerProviderWithMap([ SampleEventA::class => [ $listener_1, $listener_2 ], SampleEventC::class => [ $listener_1, $listener_2 ], ]), function (object $event, callable $listener) use ($listener_1, $listener_2): bool { if ($event instanceof SampleEventA && $listener === $listener_1) { return false; } if ($event instanceof SampleEventC && $listener === $listener_2) { return false; } return true; } );
symfony/dependency-injection的编译器传递
该软件包为symfony/dependency-injection提供编译器传递,可以自动构建一个或多个监听器提供者。
基本来说,编译器传递会搜索标记过的服务,收集它们的监听器,创建一个与它们事件的映射,并覆盖一些属性以完成服务的定义。
如果监听器分布在多个文件中,或者如果保持它们的顺序不切实际,可以为每个事件/监听器对指定优先级和相对位置,监听器将相应地排序。
添加编译器传递
<?php use Symfony\Component\DependencyInjection\ContainerBuilder; use olvlvl\EventDispatcher\Symfony\ListenerProviderPass; $container = new ContainerBuilder(); $container->addCompilerPass(new ListenerProviderPass());
默认情况下,用于识别要构建的监听器提供者的标签是listener_provider
,但它可以进行配置。
<?php use Symfony\Component\DependencyInjection\ContainerBuilder; use olvlvl\EventDispatcher\Symfony\ListenerProviderPass; $container = new ContainerBuilder(); $container->addCompilerPass(new ListenerProviderPass('my_listener_provider_tag'));
定义服务
以下示例使用PSR接口作为服务标识符,但也可以使用如my_listener_provider
这样的名称,正如我们在构建多个监听器提供者时将看到的。此外,不需要指定synthetic
属性,但建议向其他开发者表明服务定义是一个占位符。
注意:为了完成服务定义,编译器传递会覆盖synthetic
、class
和arguments
属性,但会保留任何其他属性。
services: Psr\EventDispatcher\ListenerProviderInterface: synthetic: true tags: [ listener_provider ]
默认情况下,监听器服务的标签是event_listener
,但可以进行配置,这在构建多个监听器提供者时是必需的。
services: Psr\EventDispatcher\ListenerProviderInterface: synthetic: true tags: - { name: listener_provider, listener_tag: event_listener }
以下示例演示了将自带的监听服务附加到监听提供者。它们带有默认标签 event_listener
。一个监听器可以监听多个事件,如 ListenerC
所演示的。
services: Psr\EventDispatcher\ListenerProviderInterface: synthetic: true tags: [ listener_provider ] Acme\MyApp\ListenerA: tags: - { name: event_listener, event: Acme\MyApp\EventA } Acme\MyApp\ListenerB: tags: - { name: event_listener, event: Acme\MyApp\EventB } # ListenerC listens to EventA and EventC Acme\MyApp\ListenerC: tags: - { name: event_listener, event: Acme\MyApp\EventA } - { name: event_listener, event: Acme\MyApp\EventC }
构建多个监听提供者
可以构建多个监听提供者,您只需指定每个提供者使用哪个监听器标签。
services: listener_provider_a: class: Psr\EventDispatcher\ListenerProviderInterface synthetic: true tags: - { name: listener_provider, listener_tag: event_listener_for_a } listener_provider_b: class: Psr\EventDispatcher\ListenerProviderInterface synthetic: true tags: - { name: listener_provider, listener_tag: event_listener_for_b } Acme\MyApp\ListenerA1: tags: - { name: event_listener_for_a, event: Acme\MyApp\EventA } Acme\MyApp\ListenerA2: tags: - { name: event_listener_for_a, event: Acme\MyApp\EventA } Acme\MyApp\ListenerB: tags: - { name: event_listener_for_b, event: Acme\MyApp\EventB } # ListenerM is used by both Providers A and B, # but it will only receive EventC from Provider B Acme\MyApp\ListenerM: tags: - { name: event_listener_for_a, event: Acme\MyApp\EventA } - { name: event_listener_for_b, event: Acme\MyApp\EventA } - { name: event_listener_for_b, event: Acme\MyApp\EventC }
指定优先级
编译器可以通过优先级对监听器进行排序。有效的优先级是整数,可以是正数或负数,也可以是特殊值 first
和 last
之一。使用这些特殊值,无论其他优先级如何,监听器都会被放置在首位或末位。多个监听器可以使用这些特殊值,在这种情况下,效果会叠加。在优先级相等的情况下,将保留原始顺序。
注意:如果没有指定,则优先级默认为 0,除非定义了 before
或 after
属性。
以下示例演示了如何使用 priority
属性来指定监听器的顺序。最终的顺序如下:
- 对于
SampleEventA
:listener_e
,listener_d
,listener_c
,listener_a
,listener_b
。 - 对于
SampleEventB
:listener_d
,listener_b
。
services: Psr\EventDispatcher\ListenerProviderInterface: synthetic: true tags: [ listener_provider ] listener_a: class: SampleListener tags: - name: event_listener event: SampleEventA priority: -10 listener_b: class: SampleListener tags: - name: event_listener event: SampleEventA priority: last - name: event_listener event: SampleEventB listener_c: class: SampleListener tags: - name: event_listener event: SampleEventA listener_d: class: SampleListener tags: - name: event_listener event: SampleEventA priority: first - name: event_listener event: SampleEventB priority: 10 listener_e: class: SampleListener tags: - name: event_listener event: SampleEventA priority: first
指定相对顺序
编译器可以将监听器相对于其他监听器进行排序。属性 before
允许一个监听器在另一个监听器之前,而属性 after
允许一个监听器在另一个监听器之后。
注意:一次只能使用 priority
、before
或 after
中的一个。
以下示例演示了如何使用优先级和相对顺序来排序监听器。最终的顺序如下: listener_e
,listener_d
,listener_c
,listener_b
,listener_a
。
services: Psr\EventDispatcher\ListenerProviderInterface: synthetic: true tags: [ listener_provider ] listener_a: class: SampleListener tags: - name: event_listener event: SampleEventA listener_b: class: SampleListener tags: - name: event_listener event: SampleEventA before: listener_a listener_c: class: SampleListener tags: - name: event_listener event: SampleEventA after: listener_d listener_d: class: SampleListener tags: - name: event_listener event: SampleEventA after: listener_e listener_e: class: SampleListener tags: - name: event_listener event: SampleEventA priority: first
持续集成
该项目由 GitHub actions 进行持续测试。
行为准则
本项目遵守 贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。
贡献
有关详细信息,请参阅 CONTRIBUTING。
许可证
olvlvl/event-dispatcher 采用 BSD-3-Clause 许可。