Skip to content

The EventDispatcher Component

Warning: You are browsing the documentation for Symfony 5.x, which is no longer maintained.

Read the updated version of this page for Symfony 7.2 (the current stable version).

The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.

Introduction

Object-oriented code has gone a long way to ensuring code extensibility. By creating classes that have well-defined responsibilities, your code becomes more flexible and a developer can extend them with subclasses to modify their behaviors. But if they want to share the changes with other developers who have also made their own subclasses, code inheritance is no longer the answer.

Consider the real-world example where you want to provide a plugin system for your project. A plugin should be able to add methods, or do something before or after a method is executed, without interfering with other plugins. This is not an easy problem to solve with single inheritance, and even if multiple inheritance was possible with PHP, it comes with its own drawbacks.

The Symfony EventDispatcher component implements the Mediator and Observer design patterns to make all these things possible and to make your projects truly extensible.

Take an example from the HttpKernel component. Once a Response object has been created, it may be useful to allow other elements in the system to modify it (e.g. add some cache headers) before it's actually used. To make this possible, the Symfony kernel dispatches an event - kernel.response. Here's how it works:

  • A listener (PHP object) tells a central dispatcher object that it wants to listen to the kernel.response event;
  • At some point, the Symfony kernel tells the dispatcher object to dispatch the kernel.response event, passing with it an Event object that has access to the Response object;
  • The dispatcher notifies (i.e. calls a method on) all listeners of the kernel.response event, allowing each of them to make modifications to the Response object.

Installation

1
$ composer require symfony/event-dispatcher

Note

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

Usage

See also

This article explains how to use the EventDispatcher features as an independent component in any PHP application. Read the Events and Event Listeners article to learn about how to use it in Symfony applications.

Events

When an event is dispatched, it's identified by a unique name (e.g. kernel.response), which any number of listeners might be listening to. An Event instance is also created and passed to all of the listeners. As you'll see later, the Event object itself often contains data about the event being dispatched.

Event Names and Event Objects

When the dispatcher notifies listeners, it passes an actual Event object to those listeners. The base Event class contains a method for stopping event propagation, but not much else.

See also

Read "The Generic Event Object" for more information about this base event object.

Often times, data about a specific event needs to be passed along with the Event object so that the listeners have the needed information. In such case, a special subclass that has additional methods for retrieving and overriding information can be passed when dispatching an event. For example, the kernel.response event uses a ResponseEvent, which contains methods to get and even replace the Response object.

The Dispatcher

The dispatcher is the central object of the event dispatcher system. In general, a single dispatcher is created, which maintains a registry of listeners. When an event is dispatched via the dispatcher, it notifies all listeners registered with that event:

1
2
3
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

Connecting Listeners

To take advantage of an existing event, you need to connect a listener to the dispatcher so that it can be notified when the event is dispatched. A call to the dispatcher's addListener() method associates any valid PHP callable to an event:

1
2
$listener = new AcmeListener();
$dispatcher->addListener('acme.foo.action', [$listener, 'onFooAction']);

The addListener() method takes up to three arguments:

  1. The event name (string) that this listener wants to listen to;
  2. A PHP callable that will be executed when the specified event is dispatched;
  3. An optional priority, defined as a positive or negative integer (defaults to 0). The higher the number, the earlier the listener is called. If two listeners have the same priority, they are executed in the order that they were added to the dispatcher.

Note

A PHP callable is a PHP variable that can be used by the call_user_func() function and returns true when passed to the is_callable() function. It can be a \Closure instance, an object implementing an __invoke() method (which is what closures are in fact), a string representing a function or an array representing an object method or a class method.

So far, you've seen how PHP objects can be registered as listeners. You can also register PHP Closures as event listeners:

1
2
3
4
5
use Symfony\Contracts\EventDispatcher\Event;

$dispatcher->addListener('acme.foo.action', function (Event $event) {
    // will be executed when the acme.foo.action event is dispatched
});

Once a listener is registered with the dispatcher, it waits until the event is notified. In the above example, when the acme.foo.action event is dispatched, the dispatcher calls the AcmeListener::onFooAction() method and passes the Event object as the single argument:

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Contracts\EventDispatcher\Event;

class AcmeListener
{
    // ...

    public function onFooAction(Event $event)
    {
        // ... do something
    }
}

The $event argument is the event object that was passed when dispatching the event. In many cases, a special event subclass is passed with extra information. You can check the documentation or implementation of each event to determine which instance is passed.

Registering service definitions and tagging them with the kernel.event_listener and kernel.event_subscriber tags is not enough to enable the event listeners and event subscribers. You must also register a compiler pass called RegisterListenersPass() in the container builder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;

$container = new ContainerBuilder(new ParameterBag());
// register the compiler pass that handles the 'kernel.event_listener'
// and 'kernel.event_subscriber' service tags
$container->addCompilerPass(new RegisterListenersPass());

$container->register('event_dispatcher', EventDispatcher::class);

// registers an event listener
$container->register('listener_service_id', \AcmeListener::class)
    ->addTag('kernel.event_listener', [
        'event' => 'acme.foo.action',
        'method' => 'onFooAction',
    ]);

// registers an event subscriber
$container->register('subscriber_service_id', \AcmeSubscriber::class)
    ->addTag('kernel.event_subscriber');

RegisterListenersPass resolves aliased class names which for instance allows to refer to an event via the fully qualified class name (FQCN) of the event class. The pass will read the alias mapping from a dedicated container parameter. This parameter can be extended by registering another compiler pass, AddEventAliasesPass:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;

$container = new ContainerBuilder(new ParameterBag());
$container->addCompilerPass(new AddEventAliasesPass([
    \AcmeFooActionEvent::class => 'acme.foo.action',
]));
$container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);

$container->register('event_dispatcher', EventDispatcher::class);

// registers an event listener
$container->register('listener_service_id', \AcmeListener::class)
    ->addTag('kernel.event_listener', [
        // will be translated to 'acme.foo.action' by RegisterListenersPass.
        'event' => \AcmeFooActionEvent::class,
        'method' => 'onFooAction',
    ]);

Note

Note that AddEventAliasesPass has to be processed before RegisterListenersPass.

By default, the listeners pass assumes that the event dispatcher's service id is event_dispatcher, that event listeners are tagged with the kernel.event_listener tag, that event subscribers are tagged with the kernel.event_subscriber tag and that the alias mapping is stored as parameter event_dispatcher.event_aliases. You can change these default values by passing custom values to the constructors of RegisterListenersPass and AddEventAliasesPass.

Creating and Dispatching an Event

In addition to registering listeners with existing events, you can create and dispatch your own events. This is useful when creating third-party libraries and also when you want to keep different components of your own system flexible and decoupled.

Creating an Event Class

Suppose you want to create a new event that is dispatched each time a customer orders a product with your application. When dispatching this event, you'll pass a custom event instance that has access to the placed order. Start by creating this custom event class and documenting it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace Acme\Store\Event;

use Acme\Store\Order;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * This event is dispatched each time an order
 * is placed in the system.
 */
final class OrderPlacedEvent extends Event
{
    public function __construct(private Order $order) {}

    public function getOrder(): Order
    {
        return $this->order;
    }
}

Each listener now has access to the order via the getOrder() method.

Dispatch the Event

The dispatch() method notifies all listeners of the given event. It takes two arguments: the Event instance to pass to each listener of that event and the name of the event to dispatch:

1
2
3
4
5
6
7
8
9
10
use Acme\Store\Event\OrderPlacedEvent;
use Acme\Store\Order;

// the order is somehow created or retrieved
$order = new Order();
// ...

// creates the OrderPlacedEvent and dispatches it
$event = new OrderPlacedEvent($order);
$dispatcher->dispatch($event);

Notice that the special OrderPlacedEvent object is created and passed to the dispatch() method. Now, any listener to the OrderPlacedEvent::class event will receive the OrderPlacedEvent.

Note

If you don't need to pass any additional data to the event listeners, you can also use the default Event class. In such case, you can document the event and its name in a generic StoreEvents class, similar to the KernelEvents class:

1
2
3
4
5
6
7
8
9
namespace App\Event;

class StoreEvents {

    /**
    * @Event("Symfony\Contracts\EventDispatcher\Event")
    */
    public const ORDER_PLACED = 'order.placed';
}

And use the Event class to dispatch the event:

1
2
3
use Symfony\Contracts\EventDispatcher\Event;

$this->eventDispatcher->dispatch(new Event(), StoreEvents::ORDER_PLACED);

Using Event Subscribers

The most common way to listen to an event is to register an event listener with the dispatcher. This listener can listen to one or more events and is notified each time those events are dispatched.

Another way to listen to events is via an event subscriber. An event subscriber is a PHP class that's able to tell the dispatcher exactly which events it should subscribe to. It implements the EventSubscriberInterface interface, which requires a single static method called getSubscribedEvents(). Take the following example of a subscriber that subscribes to the kernel.response and OrderPlacedEvent::class events:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace Acme\Store\Event;

use Acme\Store\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class StoreSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::RESPONSE => [
                ['onKernelResponsePre', 10],
                ['onKernelResponsePost', -10],
            ],
            OrderPlacedEvent::class => 'onPlacedOrder',
        ];
    }

    public function onKernelResponsePre(ResponseEvent $event)
    {
        // ...
    }

    public function onKernelResponsePost(ResponseEvent $event)
    {
        // ...
    }

    public function onPlacedOrder(OrderPlacedEvent $event): void
    {
        $order = $event->getOrder();
        // ...
    }
}

This is very similar to a listener class, except that the class itself can tell the dispatcher which events it should listen to. To register a subscriber with the dispatcher, use the addSubscriber() method:

1
2
3
4
5
use Acme\Store\Event\StoreSubscriber;
// ...

$subscriber = new StoreSubscriber();
$dispatcher->addSubscriber($subscriber);

The dispatcher will automatically register the subscriber for each event returned by the getSubscribedEvents() method. This method returns an array indexed by event names and whose values are either the method name to call or an array composed of the method name to call and a priority (a positive or negative integer that defaults to 0).

The example above shows how to register several listener methods for the same event in subscriber and also shows how to pass the priority of each listener method. The higher the number, the earlier the method is called. In the above example, when the kernel.response event is triggered, the methods onKernelResponsePre() and onKernelResponsePost() are called in that order.

Stopping Event Flow/Propagation

In some cases, it may make sense for a listener to prevent any other listeners from being called. In other words, the listener needs to be able to tell the dispatcher to stop all propagation of the event to future listeners (i.e. to not notify any more listeners). This can be accomplished from inside a listener via the stopPropagation() method:

1
2
3
4
5
6
7
8
use Acme\Store\Event\OrderPlacedEvent;

public function onPlacedOrder(OrderPlacedEvent $event): void
{
    // ...

    $event->stopPropagation();
}

Now, any listeners to OrderPlacedEvent::class that have not yet been called will not be called.

It is possible to detect if an event was stopped by using the isPropagationStopped() method which returns a boolean value:

1
2
3
4
5
// ...
$dispatcher->dispatch($event, 'foo.event');
if ($event->isPropagationStopped()) {
    // ...
}

EventDispatcher Aware Events and Listeners

The EventDispatcher always passes the dispatched event, the event's name and a reference to itself to the listeners. This can lead to some advanced applications of the EventDispatcher including dispatching other events inside listeners, chaining events or even lazy loading listeners into the dispatcher object.

Event Name Introspection

The EventDispatcher instance, as well as the name of the event that is dispatched, are passed as arguments to the listener:

1
2
3
4
5
6
7
8
9
10
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class MyListener
{
    public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher)
    {
        // ... do something with the event name
    }
}

Other Dispatchers

Besides the commonly used EventDispatcher, the component comes with some other dispatchers:

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version