solventt/event-dispatcher

PSR-14 兼容的事件调度器

1.0.0 2021-11-25 11:50 UTC

README

目录

  1. 安装
  2. 容器监听提供者
  3. 经典监听提供者
  4. 反射监听提供者
  5. 延迟事件
  6. 根据 PSR-14 的监听器约束
  7. 停止传播事件
  8. 使用迭代器而不是数组
  9. 创建监听器提供者

这是一个 PSR-14 兼容的事件调度器,它提供了多种确定“事件监听器”定义的方法。

注意:以下所有示例,包括 DI 容器,都是使用 php-di 库编写的。但是您可以在代码中使用任何 DI 容器。

安装

// php 7.4+
composer require solventt/event-dispatcher ^0.1

// php 8.0+
composer require solventt/event-dispatcher ^1.0

注意:如果您要使用容器监听提供者,则必须安装任何 PSR-11 兼容的 DI 容器。

容器监听提供者

此提供者仅支持监听器的 字符串 定义。监听器必须实现 __invoke 方法。它允许在调度事件时“延迟”创建可调用监听器。

记住

  • 监听器的执行顺序与您在 DI 容器定义中指定的顺序相同;
  • 此提供者不支持使用 ON/OFFADD/REMOVE 方法动态绑定/解绑监听器到/从事件

DI 容器中的“事件监听器”定义,

第一种格式

return [
    'eventsToListeners' => [
        
        FirstEvent::class => [
                InvokableListenerOne::class,
                InvokableListenerTwo::class,
        ],
        
        SecondEvent::class => [
                InvokableListenerThree::class,
                InvokableListenerFour::class,
        ],   
    ]
];

第二种格式(不指定事件,监听器事件将自动解决)

return [
    'eventsToListeners' => [
        
        InvokableListenerOne::class,
        InvokableListenerTwo::class,
        InvokableListenerThree::class,
        InvokableListenerFour::class, 
    ]
];

第三种混合格式(数组和字符串)

return [
    'eventsToListeners' => [
        
        FirstEvent::class => [
            InvokableListenerOne::class,
            InvokableListenerTwo::class,
        ],
        InvokableListenerThree::class,
        InvokableListenerFour::class,

    ]
];

DI 容器的事件调度器定义

return [
    EventDispatcherInterface::class => function (ContainerInterface $container) {
        
        $provider = new ContainerListenerProvider($container);
        
        return new EventDispatcher($provider);       
    }
];

在代码的某个地方

public function __construct(private EventDispatcherInterface $dispatcher) {}

...

$this->dispatcher->dispatch(new FirstEvent());

'eventsToListeners' - 默认情况下,由容器提供者识别的定义名称。如果您想使用不同的名称,您必须将其作为第二个参数传递给 ContainerListenerProvider 构造函数

return [
    'yourCustomName' => [
        
        FirstEvent::class => [
                InvokableListenerOne::class
                InvokableListenerTwo::class
        ],  
    ],

    EventDispatcherInterface::class => function (ContainerInterface $container) {
        
        $provider = new ContainerListenerProvider($container, 'yourCustomName');
        
        return new EventDispatcher($provider);       
    }
];

经典监听提供者

此提供者仅支持监听器的可调用定义。您可以使用以下方式将监听器分配给事件

  • 提供者构造函数
  • 和/或 ON 方法(OFF 方法解绑监听器)

使用

$provider = new ClassicListenerProvider([
    FirstEvent::class => [
        new InvokableClass(),
        [ArrayCallable::class, 'test'],
        [new ArrayCallable(), 'test2']
    ],    
    
    SecondEvent::class => [    
        'usualFunc',
         $someClosure                
    ]
]);

$dispatcher = new EventDispatcher($provider);

$dispatcher->dispatch(new FirstEvent());

如果您使用 DI 容器,您的定义可能如下所示

return [
    EventDispatcherInterface::class => function (ContainerInterface $container) {
    
        $someClosure = function (FirstEvent $event): void {};
        
        $provider = new ClassicListenerProvider([
            SomeEvent::class => [
                new InvokableClass(),
                [ArrayCallable::class, 'test'],
                [new ArrayCallable(), 'test2'],
                'usualFunc',
                $someClosure                
            ],
        ]);
        
        return new EventDispatcher($provider);       
    }
];

此外,您还可以指定监听器执行顺序(整数参数)。值越高,调用越早

...
[
    SomeEvent::class => [
        [new InvokableClass(), 3],
        [[ArrayCallable::class, 'test'], 6],
        [[new ArrayCallable(), 'test2'], 10],
        ['usualFunc', 1],
        [$someClosure, 12]                
    ],
]
...

在代码的某个地方使用 ON 方法定义“事件监听器”

public function __construct(private EventDispatcherInterface $dispatcher) {}

...

$this->dispatcher->on(FirstEvent::class, new InvokableClass());

$this->dispatcher->on(SecondEvent::class, [ArrayCallable::class, 'test']);

$this->dispatcher->on(ThirdEvent::class, function (ThirdEvent $event): void {});

以及优先级

$this->dispatcher->on(FirstEvent::class, new InvokableClass(), 4);

要从一个事件中解绑监听器,请使用 OFF 方法

$this->dispatcher->off(FirstEvent::class, new InvokableClass());

$this->dispatcher->off(SecondEvent::class, [ArrayCallable::class, 'test']);

反射监听提供者

此提供者仅接受没有指定事件的可调用定义的监听器(它将自动解决)。

您可以使用以下方式将监听器分配给事件

  • 提供者构造函数
  • 和/或 ADD 方法(REMOVE 方法解绑监听器)

使用

$provider = new ReflectionListenerProvider([
    new InvokableClass(),
    [ArrayCallable::class, 'test'],
    [new ArrayCallable(), 'test2'],
    'usualFunc',
    $someClosure                          
]);

$dispatcher = new EventDispatcher($provider);

$dispatcher->dispatch(new FirstEvent());

以及监听器执行顺序

...
[
    [new InvokableClass(), 0],
    [[ArrayCallable::class, 'test'], 2],
    [new ArrayCallable(), 'test2'],
    ['usualFunc', 3],  // will be executed the first
    $someClosure       // 0 - a default priority, if it isn't specified explicitly                   
]
...

在代码的某个地方使用 ADD 方法定义“事件监听器”

public function __construct(private EventDispatcherInterface $dispatcher) {}

...

$this->dispatcher->add(new InvokableClass());

$this->dispatcher->add([ArrayCallable::class, 'test']);

$this->dispatcher->add(function (ThirdEvent $event): void {});

以及优先级

$this->dispatcher->add(new InvokableClass(), 4);

要从一个事件中解绑监听器,请使用 REMOVE 方法

$this->dispatcher->remove(new InvokableClass());

$this->dispatcher->remove([ArrayCallable::class, 'test']);

延迟事件

在某些情况下,您可能需要使用延迟事件。

public function __construct(private EventDispatcherInterface $dispatcher) {}

...

$this->dispatcher->defer(new FirstEvent());  // no listeners are called;

$this->dispatcher->defer(new SecondEvent()); // no listeners are called;

$this->dispatcher->dispatchDeferredEvents(); // FirstEvent and SecondEvent are dispatched

根据 PSR-14 的监听器约束

PSR-14 的引用

  1. "监听器必须有一个且仅有一个参数,即它响应的事件";
  2. "监听器应有一个空的返回值,并且应显式地使用类型提示返回值"。

您可以说,SHOULD 不等于 MUST。但是我想不出可能需要从监听器返回值并使用它们的使用场景。无论如何,这个调度器没有实现它。

因此,在这个包中,您不能指定

  • 空监听器签名;
  • 监听器签名中的参数多于一个;
  • 除了void之外的任何返回类型。省略返回类型也是不允许的

您必须提供监听器参数的类型提示 - 它可以是现有的类或object类型。不接受其他类型提示。

在这些情况下,将抛出异常

// Listeners callbacks

public function noParameters(): void {}

public function moreThanOneParameter(FirstEvent $event, string $name): void {}

public function undefinedParameterType($event): void {}

public function noReturnType(object $event) {}

public function wrongReturnType(FirstEvent $event): string {}

但是,您可以关闭这些监听器约束。在某个监听器提供者的构造函数中传递false是必要的

$provider = new ContainerListenerProvider($container, 'someDefinitionName', false);
...
$provider = new ClassicListenerProvider([...definition...], false);

然而,未提供相应事件的监听器会忽略此设置,因为它需要类型提示的参数来解析事件

return [
    'eventsToListeners' => [
        
        FirstEvent::class => [InvokableListenerOne::class], // supports switching off the listener constraints
        InvokableListenerTwo::class, // doesn't support switching off the listener constraints
    ]
];

此外,ReflectionListenerProvider不支持关闭监听器约束。

停止传播事件

您的事件可以实现PSR-14的StoppableEventInterface(来自PSR-14),以对监听器执行有更多的控制。例如,我们有一个以下的事件类

class FirstEvent implements StoppableEventInterface
{
    public string $result = '';

    public function isPropagationStopped(): bool
    {
        return (bool) preg_match('/stop/', $this->result);
    }
}

并且有三个监听器

public function __invoke(FirstEvent $event): void
{
    $event->result = 'First';
}
...
public function __invoke(FirstEvent $event): void
{
    $event->result .= '-stop';
}
...
public function __invoke(FirstEvent $event): void
{
    $event->result .= '-Test';
}

监听器定义

return [
    'eventsToListeners' => [
           
        FirstEvent::class => [
                InvokableListenerOne::class,
                InvokableListenerTwo::class,   // adds the '-stop' string in the $result property of FirstEvent
                InvokableListenerThree::class, // will not be executed
        ],  
    ],
    
    EventDispatcherInterface::class => function (ContainerInterface $container) {
        
        $provider = new ContainerListenerProvider($container);
        
        return new EventDispatcher($provider);    
    }
];

第三个监听器将不会执行,因为FirstEventisPropagationStopped()方法在第二个监听器执行后返回true

使用迭代器而不是数组

提供者也会在它们的构造函数中收到迭代器。但迭代器必须实现ArrayAccess接口。

示例1

$definition = [
    FirstEvent::class => [
        new InvokableClass(),
        [ArrayCallable::class, 'test'],
    ]
];

$provider = new ClassicListenerProvider(new ArrayIterator($definition));

示例2

class ListenerIterator extends ArrayIterator
{
    public function __construct()
    {
        parent::__construct($this->getListeners());
    }

    private function getListeners(): array
    {
        return [
            FirstEvent::class => [
                new InvokableClass(),
                [[ArrayCallable::class, 'test'], 2],
            ],

            SecondEvent::class => [
                'usualFunc',
                [new ArrayCallable(), 'test2']
            ]
        ];
    }
}

在代码的某个地方

$provider = new ClassicListenerProvider(new ListenerIterator());

创建监听器提供者

您需要实现PSR-14的ListenerProviderInterface。如果您的提供者想要支持ON/OFF方法,您还必须实现SubscribingInterface