> */ private array $deferred = []; private ?string $activeExecution = null; private int $dispatchDepth = 0; public function __construct( private readonly EventListenerRegistry $registry, private readonly ContainerInterface $container, private readonly LoggerInterface $logger, ) { } public function dispatch(Event $event): void { if (++$this->dispatchDepth > 32) { --$this->dispatchDepth; throw new \RuntimeException('Event dispatch recursion limit exceeded.'); } try { $this->invoke($event, DeliveryMode::Immediate); if ($this->registry->listeners($event->getName(), DeliveryMode::Deferred) !== []) { if ($this->activeExecution === null) { throw new \LogicException('Deferred events require an active execution scope.'); } $this->deferred[$this->activeExecution][] = $event; } } finally { --$this->dispatchDepth; } } public function beginExecution(string $executionId): void { if ($this->activeExecution !== null) { throw new \LogicException('An event execution scope is already active.'); } $this->activeExecution = $executionId; $this->deferred[$executionId] = []; } public function processDeferred(string $executionId): DeferredProcessingResult { if ($this->activeExecution !== $executionId) { throw new \LogicException('Cannot process deferred events for an inactive execution.'); } $processed = 0; $deadline = microtime(true) + 1.0; $deadlineExceeded = false; $limitExceeded = false; while (($event = array_shift($this->deferred[$executionId])) !== null) { if ($processed >= 1000) { $limitExceeded = true; array_unshift($this->deferred[$executionId], $event); break; } if (microtime(true) >= $deadline) { $deadlineExceeded = true; array_unshift($this->deferred[$executionId], $event); break; } $processed += $this->invoke($event, DeliveryMode::Deferred); } $remaining = count($this->deferred[$executionId]); unset($this->deferred[$executionId]); $this->activeExecution = null; return new DeferredProcessingResult( $processed, $remaining, $deadlineExceeded, $limitExceeded, ); } public function discardDeferred(string $executionId): void { unset($this->deferred[$executionId]); if ($this->activeExecution === $executionId) { $this->activeExecution = null; } } private function invoke(Event $event, DeliveryMode $delivery): int { $processed = 0; foreach ($this->registry->listeners($event->getName(), $delivery) as $listener) { if ($event->isPropagationStopped()) { break; } try { $service = $this->container->get($listener->service); $service->{$listener->method}($event); $processed++; } catch (\Throwable $error) { $this->logger->error('Event listener failed.', [ 'event' => $event->getName(), 'module' => $listener->module, 'listener' => $listener->service . '::' . $listener->method, 'exception' => $error, ]); if ($listener->failurePolicy === FailurePolicy::Propagate) { throw $error; } } } return $processed; } }