Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

The EventDispatcher System

The EventDispatcher System

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

EVERY Symfony request already invisibly runs through an event system – time to make it visible, before chapter 36 uses our OWN events for it, triggering notifications on new task assignments.

What is the observer/EventDispatcher pattern?

Instead of code A DIRECTLY calling code B (tight coupling), code A "sends" an EVENT out into the world, WITHOUT knowing WHO (if anyone) reacts to it. Any number of "listeners" can register for the SAME event, WITHOUT the triggering code knowing anything about it – loose coupling as the core benefit.

Kernel events: Symfony's own events in the request lifecycle

EVERY HTTP request triggers a FIXED sequence of kernel events – the same events that INTERNALLY make e.g. the security firewall (block 5) or error pages (chapter 41) work:

EventTiming
kernel.requestRight at the START, before the controller runs – e.g. the security firewall hooks in here.
kernel.controllerRight before the actual controller gets called.
kernel.responseAFTER the controller has returned a response, before it's sent out.
kernel.exceptionWhen an unhandled exception occurs during processing – e.g. error pages get produced here.
php bin/console debug:event-dispatcher

Shows ALL registered listeners for EVERY event, in the ORDER they execute – invaluable for understanding WHAT actually happens on a request, especially with several installed bundles.

A simple listener for kernel.exception

src/EventListener/ExceptionListener.php
<?php

declare(strict_types=1);

namespace App\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;

#[AsEventListener(event: 'kernel.exception')]
class ExceptionListener
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {
    }

    public function __invoke(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();

        $this->logger->critical('Unhandled exception: ' . $exception->getMessage());
    }
}

#[AsEventListener] registers this class AUTOMATICALLY thanks to autoconfigure: true (chapter 34) – NO manual entry in services.yaml needed. __invoke() makes the class itself "callable" like a function – a common PHP pattern for classes with EXACTLY ONE core responsibility.

The event object: a carrier of context AND control

ExceptionEvent doesn't just carry INFORMATION (the thrown exception), it also lets listeners INFLUENCE the FURTHER behavior:

use Symfony\Component\HttpFoundation\Response;

public function __invoke(ExceptionEvent $event): void
{
    $exception = $event->getThrowable();

    if ($exception instanceof \App\Exception\ProjectLimitExceededException) {
        $event->setResponse(new Response('Project limit reached.', 429));
    }
}

setResponse() OVERRIDES the default error handling with a custom response – a powerful tool, which we deliberately only mention in passing, since custom business events (chapter 36) are the better choice for MOST use cases.

Priority, when several listeners react to the SAME event

#[AsEventListener(event: 'kernel.exception', priority: 10)]
class HighPriorityListener { /* runs FIRST */ }

#[AsEventListener(event: 'kernel.exception', priority: -10)]
class LowPriorityListener { /* runs LAST */ }

HIGHER priority runs FIRST (default: 0) – important when the order of several listeners for the SAME event actually matters, e.g. when one listener should be able to stop processing for subsequent listeners.

Tipp: Kernel events are the FOUNDATION MANY Symfony bundles themselves build on (security, routing, even Twig rendering) – for our OWN business logic (task assignment, chapter 36), we instead build OUR OWN, named events, following EXACTLY the same pattern.