本文档的目的是描述Event Dispatcher规范背后的基本原理和逻辑。
许多库,组件和框架都有很长的支持机制,允许任意第三方代码与它们进行交互。大多数是经典观察者模式的变体,通常通过中间对象或服务进行调解。其他人采用更面向方面编程(AOP)的方法。尽管如此,它们都具有相同的基本概念:在固定点中断程序流,以向任意第三方库提供有关正在执行的操作的信息,并允许它们对程序行为做出反应或影响。
这是一个成熟的模型,但是库这样做的标准机制将允许它们与越来越多的第三方库进行互操作,而原始开发人员和扩展开发人员只需要更少的工作量。
工作组根据各种系统中的野外情况,确定了四种可能的事件传递工作流程。
在进一步审查时,Working Goup确定:
虽然在概念上单向通知可以异步完成(包括通过队列延迟),但实际上,该模型的显式实现很少,从而提供了更少的地方来绘制细节指导(例如正确的错误处理)。经过充分考虑后,工作组选择不为单向通知提供明确单独的工作流程,因为它可以充分表现为其他通道的退化情况。
最初,工作组希望将所有事件定义为不可变消息对象,类似于PSR-7。然而,除了单向通知案例之外,所有这些都证明是有问题的。在其他场景中,Listeners需要一种将数据返回给调用者的方法。从概念上讲,有三种可能的途径:
with*()
PSR-7和PSR-13等方法)并且监听器返回事件传递。但是,可停止事件(替代链式情况)也需要有一个通道,通过该通道指示不应该调用更多的监听器。这可以通过以下方式完成:
stopPropagation()
方法)true
或false
)返回sentinel值以指示传播应该终止。withPropagationStopped()
)这些替代方案中的每一种都有缺点。第一个意味着,至少为了指示传播状态,事件必须是可变的。第二个要求Listeners返回一个值,至少当它们打算停止事件传播时; 这可能会影响现有的图书馆,以及文件方面的潜在问题。第三个要求Listeners在所有情况下都返回Event或mutated Event,并要求Dispatchers进行测试以确保返回的值与传递给Listener的值的类型相同; 它有效地对消费者和实施者施加了责任,从而引发了更多潜在的整合问题。
另外,期望的特征是能够基于从监听器收集的值来导出是否停止传播。(例如,当其中一个提供了某个值时,或者在其中至少有三个指示“拒绝此请求”标志或类似之后停止。)虽然技术上可以实现为可进化的对象,但这种行为是本质上是有状态的,因此对于实现者和用户来说都是非常麻烦的。
让听众回归可演化事件也带来了挑战。PHP或其他地方的任何已知实现都不使用该模式。它还依赖于监听器来记住返回事件(监听器作者的额外工作)并且不返回可能与以后的监听器(例如事件的子类或超类)不完全兼容的其他新对象。
不可变事件也依赖于事件作者来尊重警告是不可变的。事实上,事件的设计非常松散,实施者忽视规范的这一部分的可能性很高,甚至是无意中。
这留下了两个可能的选择:
通过“高礼”,我们暗示需要冗长的语法和/或实现。在前一种情况下,Listener作者需要(a)创建一个新的Event实例,其中传播标志被切换,并且(b)返回新的Event实例,以便Dispatcher可以检查它:
function (SomeEvent $event) : SomeEvent
{
// do some work
return $event->withPropagationStopped();
}
后一种情况,Dispatcher实现,需要检查返回值:
foreach ($provider->getListenersForEvent($event) as $listener) {
$returnedEvent = $listener($event);
if (! $returnedEvent instanceof $event) {
// This is an exceptional case!
//
// We now have an event of a different type, or perhaps nothing was
// returned by the listener. An event of a different type might mean:
//
// - we need to trigger the new event
// - we have an event mismatch, and should raise an exception
// - we should attempt to trigger the remaining listeners anyway
//
// In the case of nothing being returned, this could mean any of:
//
// - we should continue triggering, using the original event
// - we should stop triggering, and treat this as a request to
// stop propagation
// - we should raise an exception, because the listener did not
// return what was expected
//
// In short, this becomes very hard to specify, or enforce.
}
if ($returnedEvent instanceof StoppableEventInterface
&& $returnedEvent->isPropagationStopped()
) {
break;
}
}
在这两种情况下,我们都会引入更多潜在的边缘案例,几乎没有任何好处,并且很少有语言级机制来指导开发人员纠正实现。
鉴于这些选择,工作组认为可变事件是更安全的选择。
也就是说,不要求事件是可变的。当且仅当有必要且适合于手头的用例时,实现者才应在Event对象上提供mutator方法。
在规范开发过程中的实验确定了存在各种可行的合法手段,通过这些手段可以向Dispatcher通知监听器。听众:
这些和其他机制现在都存在于PHP中的野外,所有这些都是值得支持的有效用例,并且很少(如果有的话)可以方便地表示为另一个的特殊情况。也就是说,标准化一种方式,甚至一小组方式,告知系统监听器,如果不是不可能的话,如果不切断应该支持的许多用例,那么结果是不切实际的。
因此,工作组选择将听众的注册封装在ListenerProviderInterface
。Provider对象可以具有可用的显式注册机制,或者具有多个这样的机制,或者没有。它也可以生成由某些编译步骤生成的代码。但是,这也将管理调度事件的过程的责任从映射事件到监听器的过程分开。这样,根据需要,不同的实现可以与不同的提供者机制混合和匹配。
甚至可能允许库包含自己的提供程序,这些提供程序可以聚合到一个公共提供程序中,该提供程序聚合其侦听程序以返回到Dispatcher。这是在任意框架内处理任意监听器注册的一种可能方式,尽管工作组明确表示这不是唯一的选择。
虽然将Dispatcher和Provider组合到一个对象中是一个有效且允许的退化情况,但不建议这样做,因为它会降低系统集成商的灵活性。相反,提供者应该作为依赖对象组成。
规范要求在Dispatcher返回之前,必须调用提供者返回的可调用项(除非传播明确停止)。但是,规范还明确指出,监听器可能会将事件排入队列以供以后处理,而不是立即采取措施。提供者也完全允许接受可调用的注册,但在将其返回给Dispatcher之前将其包装在另一个可调用的中。(在这种情况下,包装器是从Dispatcher的角度来看的监听器。)这允许以下所有行为合法:
最终结果是,提供者和监听者有责任确定何时将对事件的响应推迟到稍后的安全。在这种情况下,提供者或监听者明确选择不能将有意义的数据传递回发射器,但工作组确定他们最有可能知道这样做是否安全。
虽然从技术上讲是设计的副作用,但它与Laravel(Laravel 5)使用的方法基本相同,并且已在野外得到证实。
根据规范,Dispatcher必须返回发射器传递的事件。这被指定为用户提供更符合人体工程学的体验,允许类似于以下的短手:
$event = $dispatcher->dispatch(new SomeEvent('some context'));
$items = $dispatcher->dispatch(new ItemCollector())->getItems();
该EventDispatcher::dispatch()
接口,但是,有没有指定返回类型。这主要是为了与现有实现向后兼容,以使它们更容易采用新接口。另外,由于事件可以是任何任意对象,返回类型只能是object
,它只提供最小(虽然非零)值,因为该类型声明不会为IDE提供任何有用的信息,也不会有效地强制执行相同的操作。事件被退回。因此,返回的方法在语法上是无类型的。但是,返回相同的Event对象dispatch()
仍然是一项要求,如果不这样做,则违反了规范。
活动经理工作组包括: