Skip to content

Dispatcher

The Dispatcher plugin provides low-level event dispatching using the League Event library. It serves as the foundation for the EventBus plugin.

Two ports are available:

  • DispatcherInterface (PSR-14 EventDispatcherInterface) - dispatches events to registered listeners
  • ListenerRegistryInterface (PSR-14 ListenerProviderInterface) - registers and provides listeners

Adapters:

  • LeagueDispatcher wraps League Event for event dispatching
  • LeagueListenerRegistry wraps League Event for listener registration

PSR-14 Compatibility

The plugin fully implements PSR-14 through two separate interfaces:

Phexium Port PSR-14 Interface Purpose
DispatcherInterface EventDispatcherInterface Dispatch events
ListenerRegistryInterface ListenerProviderInterface Register and provide listeners

This separation follows PSR-14's design philosophy where dispatching and listener provision are distinct concerns. Code can type-hint either the Phexium ports or the PSR-14 interfaces directly.

Why Use It

The Dispatcher is a low-level abstraction used internally by the EventBus. Application code should typically use EventBus for domain events. The Dispatcher is useful for framework internals or when generic event dispatching is needed without domain event semantics.

Application Code → EventBus (high-level) → Dispatcher + ListenerRegistry (low-level) → League Event

Usage

The EventBus delegates to both ports internally:

// Dispatching events
$this->dispatcher->dispatch($event);

// Registering listeners
$this->listenerRegistry->subscribeTo(BookCreatedEvent::class, $listener);

// PSR-14: Getting listeners for an event
$listeners = $this->listenerRegistry->getListenersForEvent($event);

Listeners implement ListenerInterface:

final readonly class BookEventHandler implements ListenerInterface
{
    public function __invoke(DomainEventInterface $event): void
    {
        // Handle event
    }
}

Container Configuration

Both ports must share the same underlying League Event registry:

PrioritizedListenerRegistry::class => fn (): PrioritizedListenerRegistry => new PrioritizedListenerRegistry(),

LeagueEventDispatcher::class => fn (ContainerInterface $c): LeagueEventDispatcher => new LeagueEventDispatcher(
    $c->get(PrioritizedListenerRegistry::class)
),

DispatcherInterface::class => fn (ContainerInterface $c): LeagueDispatcher => new LeagueDispatcher(
    $c->get(LeagueEventDispatcher::class)
),

ListenerRegistryInterface::class => fn (ContainerInterface $c): LeagueListenerRegistry => new LeagueListenerRegistry(
    $c->get(PrioritizedListenerRegistry::class)
),

Testing

Use FakeDispatcher and FakeListenerRegistry for testing:

$listenerRegistry = new FakeListenerRegistry();
$dispatcher = new FakeDispatcher($listenerRegistry);

// Register and dispatch
$listenerRegistry->subscribeTo(MyEvent::class, $listener);
$dispatcher->dispatch($event);

// Assertions
expect($dispatcher->wasDispatched(MyEvent::class))->toBeTrue();
expect($listenerRegistry->getSubscriberCount(MyEvent::class))->toBe(1);

See Also