neunerlei / event-bus
此包包含一个符合PSR-14规范的事件调度器外观,旨在使监听提供者和调度对象更紧密地结合在一起。
Requires
- php: ^7.3
- neunerlei/container-autowiring-declaration: ^1.1
- psr/container: ^1.0
- psr/event-dispatcher: ^1.0
Requires (Dev)
- crell/tukio: ^1.1
- neunerlei/dbg: ^1.9
- phpunit/phpunit: ^9.2.5
README
此包包含一个符合PSR-14规范的事件调度器外观,旨在使监听提供者和调度对象更紧密地结合在一起。
虽然我明白PSR的来源,但在我的日常生活中,注册监听器和调度事件在不同的类中似乎很奇怪。这就是这个外观的用武之地;它将调度器和提供者合并为单个类,您可以在代码中使用它。
版本 2.0
第二个版本不再依赖于Tukio库,而是提供了自己的监听提供者和调度器实现。为了提高事件密集型项目中的性能,已经移除了对Options库的依赖。
安装
使用composer安装此包
composer require neunerlei/event-bus
基本用法
您可以使用事件总线,就像使用任何其他没有PSR-14的旧式事件总线实现一样。
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Tests\Assets\FixtureEventA; $bus = new EventBus(); $bus->addListener(FixtureEventA::class, function(FixtureEventA $e){ // Do stuff :) }); $bus->dispatch(new FixtureEventA());
使用监听器优先级
您可以为一个已注册的监听器设置一个优先级,该优先级定义了它们的执行顺序。优先级可以是任何整数值,其中0是默认值。在“+”范围内定义的值具有高于默认值的优先级,而在“-”范围内定义的值则具有低于默认值的优先级。
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Tests\Assets\FixtureEventA; $bus = new EventBus(); $bus->addListener(FixtureEventA::class, function(FixtureEventA $e){ echo "3"; }, ["priority" => -10]); $bus->addListener(FixtureEventA::class, function(FixtureEventA $e){ echo "2"; }); $bus->addListener(FixtureEventA::class, function(FixtureEventA $e){ echo "1"; }, ["priority" => 10]); $bus->dispatch(new FixtureEventA());
使用基于id的排序
该库提供基于“id”的排序,这意味着您可以为您的监听器提供Ids,并在指定id之前/之后对其他监听器进行排序。事件总线也为此提供了外观。
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Tests\Assets\FixtureEventA; $bus = new EventBus(); $bus->addListener(FixtureEventA::class, function(FixtureEventA $e){ echo "2"; }, ["id" => "myId"]); $bus->addListener(FixtureEventA::class, function(FixtureEventA $e){ echo "1"; }, ["before" => "myId"]); $bus->dispatch(new FixtureEventA());
一次性事件
从版本3.0.0开始,可以注册一次性监听器。这意味着监听器在执行一次后将从列表中自动删除。此功能与所有使用的监听提供者无缝协作。
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Tests\Assets\FixtureEventA; $bus = new EventBus(); $bus->addListener(FixtureEventA::class, static function(){ echo 'I\'m a special kind of noodle!'; }, ['once']); $bus->dispatch(new FixtureEventA()); $bus->dispatch(new FixtureEventA()); // The string will only show up once
使用事件订阅者
事件订阅者的概念是(如果我不对的话,请纠正我)主要由Symfony推广的。它为您提供了一种统一的方法,以便在单个服务中拥有多个监听器。
该总线为您提供两种注册事件订阅者的选项。
选项 1:现有实例
如果您已经有了订阅者的实例,则此选项是最佳选择。订阅者类必须实现EventSubscriberInterface
接口才有效。
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Subscription\EventSubscriberInterface; use Neunerlei\EventBus\Subscription\EventSubscriptionInterface; use Neunerlei\EventBus\Tests\Assets\FixtureEventA; class MyEventSubscriber implements EventSubscriberInterface { /** * @inheritDoc */ public function subscribeToEvents(EventSubscriptionInterface $subscription){ // You only have to define the method you want to trigger. $subscription->subscribe(FixtureEventA::class, "onEventHappens"); } public function onEventHappens(FixtureEventA $event){ // Do Stuff } } $bus = new EventBus(); $bus->addSubscriber(new MyEventSubscriber()); $bus->dispatch(new FixtureEventA());
选项 2:延迟实例化
第二个选项用于延迟注册服务。这意味着您不需要提供订阅者的实例,而是将类名传递给事件总线。在这种情况下,实例仅在其中的一个订阅事件被调度时才会创建。
要延迟创建实例,您可以提供一个工厂函数(以下示例),该函数应返回订阅者的实例。创建服务实例的另一种方法是使用应传递给事件总线构造函数的PSR-11容器对象。如果事件总线有一个容器实现,则工厂定义是可选的。
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Subscription\EventSubscriptionInterface; use Neunerlei\EventBus\Subscription\LazyEventSubscriberInterface; use Neunerlei\EventBus\Tests\Assets\FixtureEventA; class MyEventSubscriber implements LazyEventSubscriberInterface { /** * @inheritDoc */ public static function subscribeToEvents(EventSubscriptionInterface $subscription){ // You only have to define the method you want to trigger. $subscription->subscribe(FixtureEventA::class, "onEventHappens"); } public function onEventHappens(FixtureEventA $event){ // Do Stuff } } $bus = new EventBus(); // The second argument (factory) is used to instantiate the event subscriber object when it is required. $bus->addLazySubscriber(MyEventSubscriber::class, function(){ new MyEventSubscriber(); }); $bus->dispatch(new FixtureEventA()); // Example using a container $container = new FancyContainer(); // Use the instance of your PSR-11 container here $bus = new EventBus($container); // The factory is now no longer required and the instance will be resolved using the container. $bus->addLazySubscriber(MyEventSubscriber::class); $bus->dispatch(new FixtureEventA());
修改混凝土调度器、监听器提供者和容器
如上所述,我们内部使用自己的调度器和监听器提供者实现来提供总线的主要逻辑。然而,事件总线类被设计为与您想要使用的PSR-14实现无关。
您可以通过使用API来覆盖内部容器、监听器提供者和调度器对象。
<?php use Neunerlei\EventBus\EventBus; $bus = new EventBus(); // Retrieve the current dispatcher implementation $dispatcher = $bus->getConcreteDispatcher(); // Replace the dispatcher with another implementation $bus->setConcreteDispatcher(new \Crell\Tukio\Dispatcher(new \Crell\Tukio\OrderedListenerProvider())); // Retrieve the current implementation for the listener provider $listenerProvider = $bus->getConcreteListenerProvider(); // Replace the listener provider with another implementation $bus->setConcreteListenerProvider(new \Crell\Tukio\OrderedListenerProvider()); // Retrieve the current container instance // This will be null, be cause there was no bus given to the constructor. $container = $bus->getContainer(); // Replace the container with another implementation, or set one if the container // did not exist at the time when the event bus was instantiated. $bus->setContainer(new FancyContainer());
监听器提供者适配器
我在编写外观时遇到的一个问题是,PSR-14没有定义添加监听器的统一合同(这实际上是其优势之一)。因此,我们必须提供一个适配器,将注册转换到正确的监听器提供者实现。适配器可以注册在类或接口基础上,可以是任何可调用的。下面的示例展示了Tukio库(由Larry Garfield提供)的示例实现,该适配器已经内置。
要使用此库,请使用composer安装它。
composer require crell/tukio ^1.1
之后,您可以如此注册提供者适配器:重要提示:这是一个如何实现您自己的适配器的示例,您不需要为crell/tukio实现这样做!
<?php use Crell\Tukio\Dispatcher; use Crell\Tukio\OrderedListenerProvider; use Crell\Tukio\OrderedProviderInterface; use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Dispatcher\EventListenerListItem; $bus = new EventBus(); $orderedProvider = new OrderedListenerProvider(); $bus->setConcreteDispatcher(new Dispatcher($orderedProvider)); $bus->setConcreteListenerProvider($orderedProvider); $bus->setProviderAdapter(OrderedProviderInterface::class, static function ( OrderedProviderInterface $provider, string $event, EventListenerListItem $item, array $options ) { if ($item->pivotId === null) { return $provider->addListener($item->listener, $item->priority, $item->id, $event); } if ($item->beforePivot) { return $provider->addListenerBefore($item->pivotId, $item->listener, $item->id, $event); } return $provider->addListenerAfter($item->pivotId, $item->listener, $item->id, $event); } );
适配器接收四个参数。
- $provider是具体提供者实现的实例
- $event是给定$listener应绑定的单个事件类
- $item是准备好的配置对象,用于注册事件监听器。包含解析后的默认选项。
- $options是传递的原始$options数组,您可以根据需要实现选项验证。
一次性事件(once)如果您的监听器提供者支持一次性事件,基本相当于前端应用程序中的"once",您可以将setProviderAdapter()的第三个参数设置为true,这意味着您的提供者将内部处理此类事件。否则,事件总线将自动使用我们的内部ListenerProviderOnceProxy作为后备。
请记住,如果您告诉事件总线您的提供者处理"once",则您的适配器必须将此功能转换到您的实际提供者。这可能看起来像这样
<?php use Neunerlei\EventBus\EventBus; use Neunerlei\EventBus\Dispatcher\EventListenerListItem; $bus = new EventBus(); $bus->setProviderAdapter( ExampleProvider::class, static function ( ExampleProvider $provider, string $event, EventListenerListItem $item, array $options ) { if($item->once){ $provider->addOneTimeListener($event, $item->listener, $item->priority, $item->id); } // [...] Your other configuration }, true );
作为PSR-14调度器和/或监听器提供者使用
事件总线类实现了EventDispatcherInterface以及ListenerProviderInterface。这样,您就可以将总线实例用作任何其他PSR-14兼容项目的聚合器而不会出现问题。
可停止事件
PSR-14定义了如何处理可停止事件,因此我们为此用例提供了一个抽象。如果您想要创建一个可停止的事件,只需扩展Neunerlei\EventBus\AbstractStoppableEvent类。或者如果您扩展了一个现有的事件,您也可以使用Neunerlei\EventBus\StoppableEventTrait。
特别感谢
特别感谢LABOR.digital(这是德语中“实验室”的意思,而不是英文中的“工作”:D)使我能够在线发布我的代码。
明信片软件
您可以使用此软件包,但如果它进入您的生产环境,我将非常感谢您从您的家乡寄给我一张明信片,提到您正在使用我们的哪个软件包。
您可以在这里找到我的地址。
谢谢:D