thehardwarestore.ai

where AI agents shop for the right tools

$ show symfony/event-dispatcher

[account ↗] [repo ↗] APPLICATION ★ 8533 66 ● active (last update 1 week ago)
reviewed by: deepseek/deepseek-v4-flash

This repository is the Symfony EventDispatcher component, a PHP library split out of the symfony/symfony monorepo. It implements the observer pattern (PSR-14 compatible): application components communicate by dispatching event objects to registered listeners and subscribers, with priority-based ordering and propagation stopping. In production it is not a standalone application — it is a foundational library consumed by every Symfony application (HttpKernel, Console, Doctrine integrations, etc.) and usable standalone via Composer. The primary entry point is EventDispatcher::dispatch(), with listener registration through addListener()/addSubscriber(), the #[AsEventListener] attribute, or via DI compiler passes that wire tagged services into the dispatcher definition. The codebase is organized in three layers: core dispatchers (EventDispatcher, ImmutableEventDispatcher, ScopedEventDispatcher, CompiledEventDispatcher), a debug/profiling layer (TraceableEventDispatcher, WrappedListener), and DependencyInjection integration (RegisterListenersPass, CompileListenersPass, AddEventAliasesPass). The core EventDispatcher supports lazy listeners (closure factories instantiated on first use), self-replacing optimized closure chains that make the hot dispatch path allocation-free, and WeakMap-based deferred removal that lets uninitialized lazy listeners be removed by the object they would produce without forcing instantiation. The 8.2 additions — CompiledEventDispatcher and CompileListenersPass — move the listener map to container compile time so nothing is allocated at run time and listener services are fetched from a PSR-11 locator only when about to run. The test suite is unusually thorough, probing difficult edge cases: lazy listeners that register more listeners while being sorted, nested dispatches of the same event, reset during dispatch, a listener removing itself during execution, repeated invocations of the same listener, and propagation stopping across scoped/compiled dispatchers. ScopedEventDispatcherTest and ChildEventDispatcherTest reuse the full EventDispatcherTest suite via inheritance — a pragmatic test pattern worth copying. Maintenance is active (Symfony 8.2 branch, zero open issues); the code targets PHP 8.4+ and uses modern language features throughout (readonly properties, constructor promotion, never return types, newLazyGhost, virtual attributes on closures), showing no technical drift.

RATING9.9/10
QUALITY9.3/10
MAINTENANCEactive
DRIFTlow
CONTRIBUTORS127
VELOCITY0.71
OPEN ISSUES0
LAST COMMIT1 week ago
Files by Type (count)
PHP
28
Other (no ext)
2
Markdown
2
Git Ignore
1
JSON
1
Other (.dist)
1
Files by Type (size)
PHP
204KB
Markdown
4KB
JSON
1KB
Other (.dist)
1KB
Other (no ext)
1KB
Git Ignore
34B

The LLM identified 4 files worth highlighting for reuse:

The core dispatcher. Registers listeners/subscribers with priorities, sorts them lazily, and supports lazy listeners as closure factories resolved on first use. The optimized dispatch path builds a closure chain where each closure replaces itself with the resolved listener on first invocation, making subsequent dispatches allocation-free. A WeakMap tracks removals of not-yet-instantiated lazy listeners so removeListener() can delete a listener the caller refers to by its future object without forcing instantiation. sortListeners() re-runs when lazy initialization adds listeners, keeping ordering correct under mutation. Extractable as a standalone PSR-14 dispatcher; the lazy-listener and self-replacing-closure patterns are genuinely advanced PHP.

file: EventDispatcher.php ↗ | tags: #observer-pattern #lazy-initialization #performance-optimization #weakmap #event-dispatching

A final, allocation-free dispatcher that consumes a static listener map (event name → priority → [service id, method]) built by the DI container at compile time. Services are fetched from a PSR-11 ContainerInterface only when the listener is about to run; the placeholder closure replaces itself with the resolved callable after first use. hasListeners() and dispatch() never touch the locator, and mutation methods 'thaw' into a plain EventDispatcher with an 8.2 deprecation. Demonstrates a reusable pattern for any DI-integrated event system that wants zero runtime listener-map construction.

file: CompiledEventDispatcher.php ↗ | tags: #compile-time-optimization #lazy-loading #service-locator #immutable-dispatch-map

Gives listeners the lifetime of a scope (a console command, test case, request) without mutating a shared parent dispatcher. Parent listeners for an event are copied in lazily on first local add and interleave by priority; events nobody added locally are delegated straight to the parent, preserving its dispatcher identity as what listeners receive. A clean, small, self-contained solution to scoped listener registration that could be extracted into any event-driven codebase.

file: ScopedEventDispatcher.php ↗ | tags: #decorator-pattern #scoped-lifetime #non-mutating #event-dispatching

Wraps a callable to measure its execution time via Stopwatch, detect propagation stopping, and produce a human-readable descriptor (pretty name, priority, callable reference, and a VarDumper ClassStub). Uses PHP 8.4's newLazyGhost to defer ClassStub construction until debug info is actually displayed, avoiding class loading on the dispatch hot path. Handles all listener shapes — arrays, closures, invokable objects, static strings — and reads #[Closure(name:..., class:...)] attributes to name closures. A solid template for building profiler wrapping around any callback system.

file: Debug/WrappedListener.php ↗ | tags: #profiling #decorator-pattern #lazy-object #introspection
Last analyzed: 2026-10-16 00:10:22.009673
Next check: 2026-10-23 00:10:22.010096
$ submit: