Event Dispatching is a common and well-tested mechanism to allow developers to inject logic into an application easily and consistently.
The goal of this PSR is to establish a common mechanism for event-based extension and collaboration so that libraries and components may be reused more freely between various applications and frameworks.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Having common interfaces for dispatching and handling events allows developers to create libraries that can interact with many frameworks and other libraries in a common fashion.
Some examples:
Events are objects that act as the unit of communication between an Emitter and appropriate Listeners.
Event objects MAY be mutable should the use case call for Listeners providing information back to the Emitter. However, if no such bidirectional communication is needed then it is RECOMMENDED that the Event be defined as immutable; i.e., defined such that it lacks mutator methods.
Implementers MUST assume that the same object will be passed to all Listeners.
It is RECOMMENDED, but NOT REQUIRED, that Event objects support lossless serialization and deserialization; $event == unserialize(serialize($event))
SHOULD hold true. Objects MAY leverage PHP’s Serializable
interface, __sleep()
or __wakeup()
magic methods, or similar language functionality if appropriate.
A Stoppable Event is a special case of Event that contains additional ways to prevent further Listeners from being called. It is indicated by implementing the StoppableEventInterface
.
An Event that implements StoppableEventInterface
MUST return true
from isPropagationStopped()
when whatever Event it represents has been completed. It is up to the implementer of the class to determine when that is. For example, an Event that is asking for a PSR-7 RequestInterface
object to be matched with a corresponding ResponseInterface
object could have a setResponse(ResponseInterface $res)
method for a Listener to call, which causes isPropagationStopped()
to return true
.
A Listener may be any PHP callable. A Listener MUST have one and only one parameter, which is the Event to which it responds. Listeners SHOULD type hint that parameter as specifically as is relevant for their use case; that is, a Listener MAY type hint against an interface to indicate it is compatible with any Event type that implements that interface, or to a specific implementation of that interface.
A Listener SHOULD have a void
return, and SHOULD type hint that return explicitly. A Dispatcher MUST ignore return values from Listeners.
A Listener MAY delegate actions to other code. That includes a Listener being a thin wrapper around an object that runs the actual business logic.
A Listener MAY enqueue information from the Event for later processing by a secondary process, using cron, a queue server, or similar techniques. It MAY serialize the Event object itself to do so; however, care should be taken that not all Event objects may be safely serializable. A secondary process MUST assume that any changes it makes to an Event object will NOT propagate to other Listeners.
A Dispatcher is a service object implementing EventDispatcherInterface
. It is responsible for retrieving Listeners from a Listener Provider for the Event dispatched, and invoking each Listener with that Event.
A Dispatcher:
If passed a Stoppable Event, a Dispatcher
isPropagationStopped()
on the Event before each Listener has been called. If that method returns true
it MUST return the Event to the Emitter immediately and MUST NOT call any further Listeners. This implies that if an Event is passed to the Dispatcher that always returns true
from isPropagationStopped()
, zero listeners will be called.A Dispatcher SHOULD assume that any Listener returned to it from a Listener Provider is type-safe. That is, the Dispatcher SHOULD assume that calling $listener($event)
will not produce a TypeError
.
An Exception or Error thrown by a Listener MUST block the execution of any further Listeners. An Exception or Error thrown by a Listener MUST be allowed to propagate back up to the Emitter.
A Dispatcher MAY catch a thrown object to log it, allow additional action to be taken, etc., but then MUST rethrow the original throwable.
A Listener Provider is a service object responsible for determining what Listeners are relevant to and should be called for a given Event. It may determine both what Listeners are relevant and the order in which to return them by whatever means it chooses. That MAY include:
Any combination of the above, or other mechanisms, MAY be used as desired.
Listener Providers SHOULD use the class name of an Event to differentiate one event from another. They MAY also consider any other information on the event as appropriate.
Listener Providers MUST treat parent types identically to the Event's own type when determining listener applicability. In the following case:
class A {}
class B extends A {}
$b = new B();
function listener(A $event): void {};
A Listener Provider MUST treat listener()
as an applicable listener for $b
, as it is type compatible, unless some other criteria prevents it from doing so.
A Dispatcher SHOULD compose a Listener Provider to determine relevant listeners. It is RECOMMENDED that a Listener Provider be implemented as a distinct object from the Dispatcher but that is NOT REQUIRED.
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;
}