symfony/event-dispatcher-contracts
A small set of PHP interfaces that abstract Symfony's event dispatcher, letting you build on proven event semantics and swap implementations without locking your code to a specific package.
Architecture fit
symfony/event-dispatcher-contracts is an interfaces-and-traits-only package: it ships EventDispatcherInterface, Event, StoppableEventInterface, and ListenerProviderInterface (PSR-14 semantics) with no dispatching implementation. That makes it a contract layer, not a feature. For Laravel, the fit is asymmetric:
Illuminate\Contracts\Events\Dispatcher is not PSR-14 compliant (different dispatch() signature/return, EventServiceProvider registration instead of a ListenerProvider, no StoppableEventInterface handling). Adopting this package does not, by itself, get us interoperability — it only gets us a shared vocabulary.The "Dependents: 0" figure should be treated as unreliable metadata, not a signal of low adoption — this package is overwhelmingly consumed transitively and rarely declared directly. Conversely, the future-dated "last release: 2026-06-27" needs verification against Packagist before we pin anything in a lockfile.
Integration feasibility
Symfony\Contracts\EventDispatcher\Event. Laravel can dispatch these objects unchanged, so the change is effectively type-hint migration with no runtime delta.dispatch() onto Laravel's dispatcher (or the reverse via spatie/laravel-psr-event-dispatcher) so external consumers can dispatch into our app.Event::fake() behavior start to erode.Technical risk
ShouldQueue, ShouldBroadcast, wildcard listeners, and Event::fake() assertions are not part of the PSR-14 contract. Swapping the dispatcher silently removes capabilities teams depend on.handle($event)) vs PSR-14 callables (__invoke($event)) create a dual-registration surface that drifts over time.Event::fake() does not observe a PSR-14 dispatcher.Key questions
Stack fit Partial and deliberate: the package slots in cleanly as the event-definition contract for Laravel 10+ on PHP 8.1+, while Laravel's dispatcher remains the runtime transport. Full PSR-14 dispatching is a separate, optional decision. Recommended posture: adopt contracts now, defer dispatcher replacement until a second framework consumer exists.
Migration path
Symfony\Contracts\EventDispatcher\Event; existing events untouched. Add a custom make:event stub and a static-analysis rule so the convention sticks without code review policing.Compatibility
Illuminate\Events APIs: at risk only in Phase 3/4, not Phase 1/2.Sequencing Pilot in one shared module with a real or realistic second consumer, gated by an ADR. Success criteria: same event class dispatching and being consumed in both a Laravel process and a non-Laravel process, with no change to queued/broadcast behavior in the host app. Expand only after that pilot clears. Budget: ~1 sprint for the pilot including adapter spike and test-double work.
Maintenance Near-zero for the package itself — interfaces only, MIT, Symfony's backward-compatibility promise, and upgrades are typically handled transitively. The real maintenance surface is our adapter and event-convention tooling, which is small but permanent. Pin the version explicitly and let Dependabot/Renovate handle bumps; interface changes are rare and major-version gated.
Support
Community-supported with a predictable Symfony release cadence; no vendor SLA. Support risk is not the package — it's the absence of an internal owner for the bridge. Assign ownership to the platform team and document the contract convention in the engineering handbook, otherwise the pattern decays within two quarters as new engineers revert to Illuminate\Events.
Scaling Runtime cost is negligible — it's an autoloaded interface set plus one indirection per dispatch through any adapter. At high event volumes (thousands of dispatches per request or per job batch), benchmark the adapter path specifically; the honest expectation is single-digit microseconds, so this is a footnote, not a blocker.
Failure modes
Event::fake().TypeError in async workers — a class of failure that surfaces in queue workers, not the web path, so it's slow to detect.Ramp-up 1–2 days for a mid-level engineer to understand the contract and the Laravel gap; the ADR and lint rule are the durable artifacts. Team-level rollout is a single knowledge-share plus a code template. The genuine ramp-up cost is organizational: convincing teams not to swap the dispatcher for its own sake.
Recommendation Adopt in Phase 1–2 form (contracts + shared event library) for modules with a credible cross-framework consumer; explicitly decline dispatcher replacement until a second framework consumer is funded. Cheapest reversible step with the highest option value, and it defers the only genuinely risky decision.
How can I help you explore Laravel packages today?