Skip to content

Implement PSR-14 dispatch in Altair\Happen (object-based, alongside name-based API) #97

@tonydspaniard

Description

@tonydspaniard

Phase 3d (AGENT.md §7). Altair\Happen predates PSR-14; this finishes the standardization.

Current state (partially done)

  • composer.json already requires psr/event-dispatcher: ^1.0.
  • Altair\Happen\Contracts\EventInterface already extends Psr\EventDispatcher\StoppableEventInterface.
  • But the dispatcher and provider do not implement the PSR-14 interfaces:
    • EventDispatcher dispatches by name: dispatch(string $name, ?EventInterface $event): EventInterface.
    • Altair\Happen\Contracts\ListenerProviderInterface is Altair's own, keyed by string event name — not PSR-14's getListenersForEvent(object $event): iterable.

Goal

Implement PSR-14's object-based interfaces alongside the existing name-based API (no BC break to the current dispatch surface):

  • Psr\EventDispatcher\EventDispatcherInterface::dispatch(object $event): object
  • Psr\EventDispatcher\ListenerProviderInterface::getListenersForEvent(object $event): iterable

Design decision to make first (the reason this is its own issue)

Altair's model is name-keyed; PSR-14 is type-keyed. Two viable shapes — pick one and document why:

  1. Separate Psr14EventDispatcher + type-keyed provider — cleanest separation; the existing name-based dispatcher stays untouched. Host apps choose which to bind.
  2. Dual-mode existing dispatcherEventDispatcher also accepts an object, routing by its class name; one class, two entry points. Less duplication, more coupling.

Either way: honour StoppableEventInterface::isPropagationStopped() on the PSR-14 path, and keep listener ordering deterministic.

Acceptance criteria

  • A class implements Psr\EventDispatcher\EventDispatcherInterface (dispatch(object): object, returns the same instance)
  • A provider implements Psr\EventDispatcher\ListenerProviderInterface
  • Stoppable propagation halts further listeners on the PSR-14 path
  • Existing name-based API unchanged (no BC break)
  • Tests cover: dispatch-by-object, propagation-stop, listener ordering; 80%+ on new code
  • composer qa + rector --dry-run green

Note

Scoped out of the Phase-4 modernization push (the rest of which is done/tracked); deferred deliberately because it's a design choice baked into a public API, not a mechanical change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions