icanboogie / event
提供事件API
Requires
- php: >=7.2
- icanboogie/accessor: ^2.0|^3.0|^4.0
- icanboogie/inflector: ^1.3|^2.0
Requires (Dev)
- phpunit/phpunit: ^8.5
README
icanboogie/event 允许您提供钩子,其他开发者可以将其附加到应用程序中,当发生特定事件时,会收到通知并采取行动。
在 ICanBoogie 中,事件通常用于修改初始参数,在操作处理之前/之后或失败时采取行动,在请求分发之前/之后采取行动或捕获异常。
安装
composer require icanboogie/event
功能亮点
- 易于实现。
- 事件具有类型。
- 事件通常具有目标对象,但也可以发出更简单的事件类型。
- 事件钩子附加到类而不是对象上,并且它们可以继承。
- 可以将事件钩子附加到在事件钩子链之后执行的 完成链。
- 可以停止事件链的执行。
观察者模式的一种变体
API使用的模式类似于 观察者模式,尽管事件钩子不是附加到对象上,而是附加到它们的类。当一个事件在一个目标对象上触发时,会使用其类的层次结构来过滤事件钩子。
考虑以下类层次结构
ICanBoogie\Operation
└─ ICanBoogie\Module\Operation\SaveOperation
└─ Icybee\Modules\Node\Operation\SaveOperation
└─ Icybee\Modules\Content\Operation\SaveOperation
└─ Icybee\Modules\News\Operation\SaveOperation
当发出一个具有 …\News\Operation\SaveOperation
实例的 ProcessEvent
时,会调用所有附加到此事件类的类的钩子,从附加到实例类(…\News\Operation\SaveOperation
)的事件钩子开始,一直到最后一个附加到其根类的钩子。
因此,当用 …\Node\Operation\SaveOperation
类实例触发 ProcessEvent
事件时,会调用附加到该类的钩子。可以考虑钩子是 继承 的。
入门指南
要发出事件,需要一个事件集合,其中包含事件钩子。因为当需要时为您创建了一个新的事件集合,所以您不需要自己设置它。仍然,如果您需要在创建事件集合时附加大量事件钩子,您可能想这样做。为此,您需要定义一个 提供者,该提供者将在需要时返回您的事件集合。
以下示例演示了如何设置一个提供者,该提供者使用由应用程序配置提供的事件钩子实例化事件集合
<?php namespace ICanBoogie; /* @var Application $app */ EventCollectionProvider::define(function() use ($app) { static $collection; return $collection ??= new EventCollection($app->configs['event']); }); # Getting the event collection $events = EventCollectionProvider::provide(); # or $events = get_events();
类型化事件
所有事件都是 Event 类的实例,因为它是一个抽象类,所以需要扩展。
以下代码演示了如何定义一个 ProcessEvent
类
<?php namespace ICanBoogie\Operation; use ICanBoogie\Event; use ICanBoogie\HTTP\Request; use ICanBoogie\HTTP\Response; use ICanBoogie\Operation; class ProcessEvent extends Event { /** * Reference to the response result property. */ public mixed $result; public function __construct( Operation $target, public readonly Request $request, public readonly Response $response, mixed &$result ) { $this->result = &$result; parent::__construct($target); } }
事件类型
如果事件有一个目标,则使用 for()
方法以及目标类或对象来获取事件。如果事件没有目标,则事件类型是事件类。
命名空间和命名
事件类应在仅针对其目标对象的唯一命名空间中定义。针对 ICanBoogie\Operation
实例的事件应在 ICanBoogie\Operation
命名空间中定义。
触发事件
使用 emit()
函数触发事件。
<?php namespace ICanBoogie; /* @var Event $event */ emit($event);
附加事件钩子
使用事件集合的 attach()
方法附加事件钩子。该 attach()
方法足够智能,可以从参数类型创建事件类型。这对于任何可调用对象都有效:闭包、可调用对象、静态类方法、函数。
以下示例演示了如何将闭包附加到 BeforeProcessEvent
事件。
<?php namespace ICanBoogie $detach = $events->attach(function(Operation\BeforeProcessEvent $event, Operation $target) { // … }); # or, if the event doesn't have a target $detach = $events->attach(function(Operation\BeforeProcessEvent $event) { // … }); $detach(); // You can detach if you no longer want to listen.
以下示例演示了如何将可调用对象附加到相同的事件类型。
<?php namespace ICanBoogie class ValidateOperation { private $rules; public function __construct(array $rules) { $this->rules = $rules; } public function __invoke(Operation\BeforeProcessEvent $event, Operation $target) { // … } } // … /* @var $events EventCollection */ /* @var $rules array<string, mixed> */ $events->attach(new ValidateOperation($rules));
将事件钩子附加到特定目标
使用 attach_to()
方法,可以将事件钩子附加到特定目标,并且仅对该目标进行调用。
<?php namespace ICanBoogie; use ICanBoogie\Routing\Controller; // … /* @var $events EventCollection */ $detach = $events->attach_to($controller, function(Controller\ActionEvent $event, Controller $target) { echo "invoked!"; }); $controller_clone = clone $controller; emit(new Controller\ActionEvent($controller_clone, …)); // nothing happens, it's a clone emit(new Controller\ActionEvent($controller, …)); // echo "invoked!" // … $detach(); // You can detach if you no longer want to listen.
附加 一次性 事件钩子
once()
方法附加的事件钩子在使用后自动断开。
<?php namespace ICanBoogie; /* @var $events EventCollection */ $n = 0; $events->once(MyEvent $event, function() use(&$n) { $n++; }); emit(new MyEvent()); emit(new MyEvent()); emit(new MyEvent()); echo $n; // 1
使用 events
配置附加事件钩子
当通过 ICanBoogie 的 icanboogie/bind-event 绑定包时,可以从 events
配置中附加事件钩子。有关更多详细信息,请参阅 icanboogie/bind-event 包。
将事件钩子附加到 完成链
完成链 在遍历事件链而未停止的情况下执行。
以下示例演示了如何将事件钩子附加到 count
事件的 完成链 以获得字符串 "0123"。如果第三个事件钩子与其他钩子定义相同,我们将获得 "0312"。
<?php namespace ICanBoogie; class CountEvent extends Event { public function __construct( public string $count = "0" ) { parent::__construct(); } } /* @var $events EventCollection */ $events->attach(function(CountEvent $event): void { $event->count .= "2"; }); $events->attach(function(CountEvent $event): void { $event->count .= "1"; }); $events->attach('count', function(CountEvent $event): void { $event->chain(function(CountEvent $event) { $event->count .= "3"; }); }); $event = emit(new CountEvent(0)); echo $event->count; // 0123
断开事件钩子链
可以使用 stop()
方法的事件钩子来中断事件钩子链的处理。
<?php use ICanBoogie\Operation; function on_event(Operation\ProcessEvent $event, Operation $operation): void { $event->rc = true; $event->stop(); }
事件分析
使用 EventProfiler 类来收集有关未使用事件和事件钩子调用的计时信息。所有时间信息都以浮点微时间计量。
<?php use ICanBoogie\EventProfiler; foreach (EventProfiler::$unused as list($time, $type)) { // … } foreach (EventProfiler::$calls as list($time, $type, $hook, $started_at)) { // … }
辅助工具
get_events()
:返回当前事件集合。如果不存在,则创建一个新的集合。emit()
:发出指定事件。
持续集成
该项目通过 GitHub actions 进行持续测试。
行为准则
本项目遵循 贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。
贡献
有关详细信息,请参阅 CONTRIBUTING。
许可
icanboogie/event 以 BSD-3-Clause 许可证发布。