PSR-14 EventDispatcher vs Custom CompilerPass for Listener Ordering
0 reputation · 07 Jun 2021, 22:55 UTC
0 reputation · 07 Jun 2021, 22:55 UTC
In TYPO3 v12, the transition from the deprecated Signal/Slot system to the PSR-14 EventDispatcher introduces a challenge regarding execution order. While Signal/Slot allowed for explicit integer priorities, the current PSR-14 implementation in TYPO3 sorts listeners by priority and then by service ID. This makes the execution sequence non-deterministic when multiple extensions register listeners for the same event, as the service ID order depends on the container compilation sequence.
For data modification pipelines where one extension must process a record before another, relying on the standard priority attribute may be insufficient across different extension load orders.
When deterministic cross-extension ordering is required, two primary architectural paths exist:
Does the TYPO3 v12 architecture favor a specific approach to restore deterministic ordering without introducing tight coupling between independent extensions? Is there a documented method to ensure a listener in Extension A always executes before a listener in Extension B regardless of the load order?
To restore deterministic ordering across independent extensions in TYPO3 v12 without introducing tight coupling, the PSR-14 priority attribute remains the primary architectural tool, but it must be used with a strict, documented priority scale. If two extensions must maintain a specific sequence and cannot agree on a priority value, a Custom CompilerPass is the technically correct way to enforce order at the container level without hard-coding dependencies in your PHP logic.
In TYPO3 v12, the EventDispatcher sorts listeners first by their assigned priority (descending) and then by their service ID. Because the service ID sequence is influenced by the order in which extensions are loaded into the container, listeners with identical priorities exhibit non-deterministic behavior. This creates a race condition where Extension A might run before Extension B in one environment, but after it in another, despite both having the same priority.
Avoid using generic priorities like 0 or 100. Instead, establish a wide-gap priority scale (e.g., increments of 100 or 1000). This allows third-party extensions to "slot in" between your required sequence without requiring a container rebuild or a code change.
If you must ensure Extension A precedes Extension B regardless of priority or load order, use a CompilerPass to manipulate the tagged services before the container is frozen. This avoids the "Orchestrator Pattern" because the services remain independent; only the container's internal map is modified.
Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface.Services.yaml or via the kernel.To verify that the ordering is truly deterministic and not dependent on the load order, perform the following checks:
Diagnostic Detail Required: Are the listeners in question modifying the same object property? If so, the recommendation shifts toward a Chain of Responsibility pattern rather than a simple event dispatcher.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.