PSR-14:事件调度程序

事件调度员

事件调度是一种常见且经过充分测试的机制,允许开发人员轻松,一致地将逻辑注入应用程序。

此PSR的目标是为基于事件的扩展和协作建立一个通用机制,以便可以在各种应用程序和框架之间更自由地重用库和组件。

本文件中的关键词“必须”,“不得”,“必须”,“应该”,“不应该”,“应该”,“不应该”,“推荐”,“可以”和“可选”按照RFC 2119中的描述进行解释

目标

具有用于调度和处理事件的公共接口允许开发人员创建可以以常见方式与许多框架和其他库交互的库。

一些例子:

  • 一种安全框架,用于在用户没有权限时阻止保存/访问数据。
  • 一个通用的整页缓存系统。
  • 扩展其他库的库,无论它们集成到哪个框架中。
  • 用于跟踪应用程序中执行的所有操作的日志包

定义

  • 事件 - 事件是由发射器产生的消息它可以是任意PHP对象。
  • 监听器 - 监听器是任何希望传递给事件的PHP可调用者。零个或多个侦听器可以传递相同的事件。如果它选择的话,监听器可以排队其他一些异步行为。
  • 发射器 - 发射器是任何希望发送事件的任意代码。这也称为“呼叫代码”。它不是由任何特定数据结构表示,而是指用例。
  • Dispatcher - Dispatcher是一个服务对象,由Emitter提供一个Event对象。Dispatcher负责确保将事件传递给所有相关的监听器,但必须推迟向监听器提供者确定负责的监听器。
  • 监听器提供者 - 监听器提供者负责确定哪些监听器与给定事件相关,但不得调用监听器本身。监听器提供者可以指定零个或多个相关的监听器。

活动

事件是充当发射器和适当的监听器之间通信单元的对象。

如果用例调用Listeners将信息提供给Emitter,则事件对象可能是可变的。但是,如果不需要这样的双向通信,则建议将事件定义为不可变的; 即,定义使其缺乏mutator方法。

实现者必须假设同一个对象将被传递给所有监听器。

推荐但不是必需的,Event对象支持无损序列化和反序列化; $event == unserialize(serialize($event))应该坚持。对象可以利用PHP的Serializable接口,__sleep()或者__wakeup()魔术方法,或者如果合适的话类似的语言功能。

可停止的事件

一个停止的事件是一个包含其他方法来防止进一步的听众被称为事件的特殊情况。它通过实施表明StoppableEventInterface

实现的事件StoppableEventInterface必须trueisPropagationStopped()它所代表的任何事件完成时返回由类的实现者来决定何时是。例如,要求PSR-7 RequestInterface对象与相应ResponseInterface对象匹配的事件可能有setResponse(ResponseInterface $res)一个监听器调用方法,这会导致isPropagationStopped()返回true

听众

监听器可以是任何PHP可调用的。监听器必须只有一个参数,即它响应的事件。监听器应该键入提示该参数具体与其用例相关; 也就是说,监听器可以对接口键入提示,以指示它与实现该接口的任何事件类型或该接口的特定实现兼容。

监听器应该有一个void返回,并且应该显式返回SHOULD类型提示。Dispatcher必须忽略Listeners的返回值。

监听器可以将操作委托给其他代码。这包括一个Listener,它是一个运行实际业务逻辑的对象的瘦包装器。

监听器可以使用cron,队列服务器或类似技术将来自事件的信息排入队列,以便稍后由辅助进程处理。它可以序列化Event对象本身; 但是,应注意并非所有Event对象都可以安全地序列化。辅助进程必须假定它对Event对象所做的任何更改都不会传播给其他Listener。

调度员

Dispatcher是一个实现的服务对象EventDispatcherInterface它负责从侦听器提供程序检索侦听器的侦听器,并使用该事件调用每个侦听器。

调度员:

  • 必须按照从ListenerProvider返回的顺序同步调用Listener。
  • 必须返回完成调用Listeners后传递的相同Event对象。
  • 在所有听众执行之前,不得返回发射器。

如果传递了一个Stoppable事件,一个Dispatcher

  • 必须在调用isPropagationStopped()每个Listener之前调用Event。如果该方法返回true它必须立即将事件返回给发射器,并且不得再调用任何进一步的监听器。这意味着,如果一个事件被传递到总是返回调度trueisPropagationStopped()零名听众将被调用。

Dispatcher应该假设从Listener Provider返回给它的任何Listener都是类型安全的。也就是说,Dispatcher应该假设调用$listener($event)不会产生TypeError

错误处理

监听器抛出的异常或错误必须阻止任何进一步监听器的执行。监听器抛出的异常或错误必须允许传播回发射器。

Dispatcher可以捕获抛出的对象来记录它,允许采取其他操作等,但是必须重新抛出原始的throwable。

听众提供者

监听器提供程序是一个服务对象,负责确定哪些监听器与给定事件相关并应该被调用。它可以确定听众的相关性以及通过它选择的任何方式返回它们的顺序。可能包括:

  • 允许某种形式的注册机制,以便实现者可以按固定顺序为事件分配监听器。
  • 根据事件的类型和实现的接口,通过反射派生适用的监听器列表。
  • 提前生成监听器的已编译列表,可在运行时查阅。
  • 实现某种形式的访问控制,以便只有当前用户具有特定权限时才会调用某些监听器。
  • 从Event引用的对象(例如Entity)中提取一些信息,并在该对象上调用预定义的生命周期方法。
  • 使用一些任意逻辑将其职责委托给一个或多个其他侦听器提供程序。

可以根据需要使用上述或其他机制的任何组合。

监听器提供者应该使用事件的类名来区分一个事件和另一个事件。他们也可以酌情考虑有关该事件的任何其他信息。

在确定监听器适用性时,监听器提供者必须将父类型与事件自身类型完全相同。在以下情况中:

class A {}

class B extends A {}

$b = new B();

function listener(A $event): void {};

监听器提供者必须将其listener()视为适用的监听器$b,因为它是类型兼容的,除非某些其他标准阻止它这样做。

对象组成

Dispatcher应该组成一个监听器提供程序来确定相关的监听器。建议将侦听器提供程序实现为Dispatcher的不同对象,但这不是必需的。

接口

namespace Psr\EventDispatcher;

/**
 * Defines a dispatcher for events.
 */
interface EventDispatcherInterface
{
    /**
     * Provide all relevant listeners with an event to process.
     *
     * @param object $event
     *   The object to process.
     *
     * @return object
     *   The Event that was passed, now modified by listeners.
     */
    public function dispatch(object $event);
}
namespace Psr\EventDispatcher;

/**
 * Mapper from an event to the listeners that are applicable to that event.
 */
interface ListenerProviderInterface
{
    /**
     * @param object $event
     *   An event for which to return the relevant listeners.
     * @return iterable[callable]
     *   An iterable (array, iterator, or generator) of callables.  Each
     *   callable MUST be type-compatible with $event.
     */
    public function getListenersForEvent(object $event) : iterable;
}
namespace Psr\EventDispatcher;

/**
 * An Event whose processing may be interrupted when the event has been handled.
 *
 * A Dispatcher implementation MUST check to determine if an Event
 * is marked as stopped after each listener is called.  If it is then it should
 * return immediately without calling any further Listeners.
 */
interface StoppableEventInterface
{
    /**
     * Is propagation stopped?
     *
     * This will typically only be used by the Dispatcher to determine if the
     * previous listener halted propagation.
     *
     * @return bool
     *   True if the Event is complete and no further listeners should be called.
     *   False to continue calling listeners.
     */
    public function isPropagationStopped() : bool;
}