Every framework had an event dispatcher and none of them shared an interface, so a library wanting to emit events either picked one or invented its own.
interface EventDispatcherInterface
{
public function dispatch(object $event);
}
interface ListenerProviderInterface
{
public function getListenersForEvent(object $event): iterable;
}
interface StoppableEventInterface
{
public function isPropagationStopped(): bool;
}
The event is any object with no name and no string identifier, which is the decision that makes it typed end to end — listeners match on the class rather than on a string somebody typoed. Separating the dispatcher from the listener provider is what lets a framework keep its own registration mechanism while a library depends only on dispatching. The value is entirely for library authors; an application gains nothing by adopting it directly.