olvlvl/event-dispatcher

PSR-14 事件调度器实现

v1.0.0 2021-07-16 20:12 UTC

This package is auto-updated.

Last update: 2024-08-29 20:49:29 UTC


README

Release Packagist Code Quality Code Coverage

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-dispatchersymfony/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_1SampleEventC$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属性,但建议向其他开发者表明服务定义是一个占位符。

注意:为了完成服务定义,编译器传递会覆盖syntheticclassarguments属性,但会保留任何其他属性。

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 }

指定优先级

编译器可以通过优先级对监听器进行排序。有效的优先级是整数,可以是正数或负数,也可以是特殊值 firstlast 之一。使用这些特殊值,无论其他优先级如何,监听器都会被放置在首位或末位。多个监听器可以使用这些特殊值,在这种情况下,效果会叠加。在优先级相等的情况下,将保留原始顺序。

注意:如果没有指定,则优先级默认为 0,除非定义了 beforeafter 属性。

以下示例演示了如何使用 priority 属性来指定监听器的顺序。最终的顺序如下:

  • 对于 SampleEventAlistener_elistener_dlistener_clistener_alistener_b
  • 对于 SampleEventBlistener_dlistener_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 允许一个监听器在另一个监听器之后。

注意:一次只能使用 prioritybeforeafter 中的一个。

以下示例演示了如何使用优先级和相对顺序来排序监听器。最终的顺序如下: listener_elistener_dlistener_clistener_blistener_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 进行持续测试。

Tests Static Analysis Code Style

行为准则

本项目遵守 贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。

贡献

有关详细信息,请参阅 CONTRIBUTING

许可证

olvlvl/event-dispatcher 采用 BSD-3-Clause 许可。