ostrolucky/app-event-dispatcher

适用于您应用域的事件调度器

v0.1 2018-01-22 23:24 UTC

This package is auto-updated.

Last update: 2024-08-27 05:20:49 UTC


README

大多数事件调度器都是设计为框架和其他库作为钩子系统使用的。这个不同。它严格用于 您的应用域。这允许它

安装

通过 Composer

composer require ostrolucky/app-event-dispatcher

要求

  • 对于基本功能,您只需要 PHP >= 5.6。
  • 如果您想使用我的编译器传递,则需要 symfony/dependency-injection:*
  • 如果您想利用编译器传递中的事件订阅者功能,则需要 symfony/event-dispatcher:*

使用方法

基本使用方法如下

$dispatcher = new Ostrolucky\AppEventDispatcher\AppEventDispatcher();
$dispatcher->attach('my.event.name', function(array $parameter1, \stdClass $parameter2) {
    var_dump($parameter1, $parameter2);
}));
$dispatcher->dispatch('my.event.name', ['hey'], new \stdClass);

如果您使用 Symfony 内置的事件调度器来调度您的域事件,我提供了您可以轻松使用的兼容替换的编译器传递。

您需要做的就是

  1. 将此事件调度器定义为服务,例如

    # services.yml:
    app.event_dispatcher:
        class: Ostrolucky\AppEventDispatcher\AppEventDispatcher
  2. 在主包中添加 RegisterListenersPass,如下所示

    class AppBundle extends Bundle
    {
        public function build(ContainerBuilder $container)
        {
            parent::build($container);
            $container->addCompilerPass(new Ostrolucky\AppEventDispatcher\Symfony\DependencyInjection\RegisterListenersPass());
        }
    }
  3. kernel.event_subscriberkernel.event_listener 标签替换为 app.event_listener。是的,您不再需要区分它们。如果它实现了 EventSubscriber 接口,它将被视为订阅者,否则它将被视为常规监听器。不用担心,当您尝试为订阅者定义标签中的事件时,它也会警告您。

  4. 在您调度事件的地方,将 event_dispatcher 服务替换为 app.event_dispatcher

详细优势

1. 更宽松的公共调度接口

与大多数事件调度器相比,此调度器鼓励您将参数签名验证的责任转移到事件监听器。您可以自由地直接传递监听器需要的任何参数。任何类型,任何数量。

在大多数调度器中,您被迫将所有参数包装成一个参数。在 Symfony 事件调度器的情况下是事件对象

// symfony event dispatcher
$event = new \Symfony\Component\EventDispatcher\GenericEvent(null, [
    'group' => null, 'user' => new User(), 'array' => [3, 5]
    ]
);
$eventDispatcher->dispatch('some.event', $event);

// vs. this dispatcher
$appEventDisdpatcher->dispatch('some.event', null, new User(), [3, 5]);

然后在监听器中,如果您想确保传递了正确的参数,在大多数调度器中,您被迫将其展开并手动检查类型

public function onSomeEvent(GenericEvent $event) {
    $arguments = $event->getArguments();
    if (isset($arguments['group']) && !$arguments['group'] instanceof Group) {
        throw new \InvalidArgumentException('Invalid group');
    }
    
    if (isset($arguments['user']) && !$arguments['user'] instanceof User) {
        throw new \InvalidArgumentException('Invalid user');
    }
    
    if (!is_array($arguments['array'])) {
        throw new \InvalidArgumentException('Invalid array');
    }
    
    /** @var Group $group */
    $group = $arguments['group'] ?? null;
    /** @var User $user */
    $user = $arguments['user'] ?? null;
    $array = $arguments['array'];
    
    // do the actual work...
}

与这个调度器相比

public function onSomeEvent(?Group $group, ?User $user, array $array) {
    // do the actual work
}

如您所见,symfony 事件调度器违反了 DRY,使得编写新的监听器非常重复,有很多样板代码。即使您决定类型安全不值得,您也跳过了所有的这些参数验证,您仍然需要编写注释,以便您的 IDE 理解您正在使用哪些参数类型。

公平地说,在 symfony 事件调度器的情况下,您被鼓励编写自定义事件类,其中您可以利用类型提示。在这种情况下,您可以将参数验证移动到此类中。然而,每次您的新的监听器需要不同的参数时,您都需要这样做,您仍然需要编写样板代码来将这些参数注入到您的新事件对象中并检索它们。这就是为什么在非库应用程序中很少这样做,而是使用一些通用的事件类。

2. 更严格的验证

由于这个分发器不是设计用来作为钩子系统的,因此它允许进行更严格的验证

  1. 当您尝试分发您未附加任何事件监听器的事件时,将提醒您。这种情况可能由多种原因造成
    • 您忘记为此事件附加事件监听器,或者在执行此过程中犯了一个错误
    • 您移除了所有监听该事件的监听器,并忘记了移除此事件的分发代码
    • 您在事件名称中输入了拼写错误
    • 您正在动态地分发事件,但没有检查是否有任何监听器正在监听它
  2. 当您尝试附加已附加的监听器,或尝试移除未附加的监听器时,将提醒您。这是您应用程序中存在错误的一个迹象,因为您的代码中某些部分正在尝试执行已经完成过的操作。

其他分发器不处理这些情况。

3. 跳过有问题的功能

由于在完全控制附加时这些功能没有太大意义,因此有目的地省略了这些有问题的功能。这使得这个分发器变得非常轻量级。

  1. 钩子。这个分发器不期望JavaScript风格的监听器,这些监听器可以阻止传播。如果您需要这样做,您可以轻松地使用单个监听器来实现,该监听器将根据您的约束重新定向调用。这使您可以确信所有附加的监听器都会始终被调用。
  2. 事件分发器内部优先级。这个分发器本身是FIFO风格的,因此它不会进行任何排序。正确的地方是在代码中附加监听器到分发器。在Symfony框架的情况下,您可以使用我的编译器传递,它将根据您指定的优先级以正确的顺序附加监听器。
  3. 事件分发器中的“事件订阅者”。这不应该由事件分发器负责,而应该由您用来向分发器附加监听器的代码负责。通过简单地遍历events|callbacks列表并将它们以常规方式附加到事件分发器,可以轻松创建自己的实现。尽管如此,这仍然支持在Symfony框架的编译器传递中。

常见问题解答(FAQ)

问题:为什么要在应用程序事件中使用分发器而不是直接进行服务调用呢?

回答:我同意大多数人做错了,他们应该使用直接的服务调用,因为使用分发器意味着更困难的调试,因为只有在运行时才知道实际附加了哪些监听器。它使得知道由常规程序流程触发的回调变得困难,因为附加通常是在分发调用之外完全无上下文进行的。尽管如此

  • 许多应用程序已经在它们的应用程序领域内大量使用分发,并受到了常规分发器的限制。移除它比用这个分发器替换他们使用的分发器更难。
  • 事件监听器的大量使用允许您进行IoC。您不需要修改执行分发的代码,只需附加新的监听器即可。这对于FSM特别有用。

问题:为什么您不提供LaxEventDispatcherInterface的实现以供作为库使用?

回答:我没有这样做是因为常规的事件分发器更适合此目的。它们的限制性公共接口和额外功能实际上在这里是加分项,因为它允许保持库的更好的向后兼容性,并允许在多个不同的第三方库监听同一事件时对过程有更多的控制。这就是为什么这个分发器仅关注在应用程序领域中使用的。