The EventDispatcher Component

The EventDispatcher component provides tools that allow your applicationcomponents to communicate with each other by dispatching events andlistening 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 becomesmore flexible and a developer can extend them with subclasses to modifytheir behaviors. But if they want to share the changes with other developerswho have also made their own subclasses, code inheritance is no longer theanswer.

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

The Symfony EventDispatcher component implements the Mediator and Observerdesign patterns to make all these things possible and to make your projectstruly extensible.

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

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

Installation

  1. $ composer require symfony/event-dispatcher

Note

If you install this component outside of a Symfony application, you mustrequire the vendor/autoload.php file in your code to enable the classautoloading mechanism provided by Composer. Readthis article for more details.

Usage

This article explains how to use the EventDispatcher features as anindependent component in any PHP application. Read the Events and Event Listenersarticle 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 alsocreated and passed to all of the listeners. As you'll see later, the Eventobject itself often contains data about the event being dispatched.

Naming Conventions

The unique event name can be any string, but optionally follows a fewnaming conventions:

  • Use only lowercase letters, numbers, dots (.) and underscores (_);
  • Prefix names with a namespace followed by a dot (e.g. order., user.);
  • End names with a verb that indicates what action has been taken (e.g.order.placed).

Event Names and Event Objects

When the dispatcher notifies listeners, it passes an actual Event objectto those listeners. The base Event class contains a method for stoppingevent propagation, but not muchelse.

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

Often times, data about a specific event needs to be passed along with theEvent object so that the listeners have the needed information. In suchcase, a special subclass that has additional methods for retrieving andoverriding information can be passed when dispatching an event. For example,the kernel.response event uses aResponseEvent, whichcontains methods to get and even replace the Response object.

The Dispatcher

The dispatcher is the central object of the event dispatcher system. Ingeneral, a single dispatcher is created, which maintains a registry oflisteners. When an event is dispatched via the dispatcher, it notifies alllisteners registered with that event:

  1. use Symfony\Component\EventDispatcher\EventDispatcher;
  2.  
  3. $dispatcher = new EventDispatcher();

Connecting Listeners

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

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

The addListener() method takes up to three arguments:

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

Note

A PHP callable is a PHP variable that can be used by thecall_user_func() function and returns true when passed to theis_callable() function. It can be a \Closure instance, an objectimplementing an __invoke() method (which is what closures are in fact),a string representing a function or an array representing an objectmethod 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. use Symfony\Contracts\EventDispatcher\Event;
  2.  
  3. $dispatcher->addListener('acme.foo.action', function (Event $event) {
  4. // will be executed when the acme.foo.action event is dispatched
  5. });

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

  1. use Symfony\Contracts\EventDispatcher\Event;
  2.  
  3. class AcmeListener
  4. {
  5. // ...
  6.  
  7. public function onFooAction(Event $event)
  8. {
  9. // ... do something
  10. }
  11. }

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

Registering Event Listeners and Subscribers in the Service Container

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

  1. use Symfony\Component\DependencyInjection\ContainerBuilder;
  2. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  3. use Symfony\Component\DependencyInjection\Reference;
  4. use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
  5. use Symfony\Component\EventDispatcher\EventDispatcher;
  6.  
  7. $containerBuilder = new ContainerBuilder(new ParameterBag());
  8. // register the compiler pass that handles the 'kernel.event_listener'
  9. // and 'kernel.event_subscriber' service tags
  10. $containerBuilder->addCompilerPass(new RegisterListenersPass());
  11.  
  12. $containerBuilder->register('event_dispatcher', EventDispatcher::class);
  13.  
  14. // registers an event listener
  15. $containerBuilder->register('listener_service_id', \AcmeListener::class)
  16. ->addTag('kernel.event_listener', [
  17. 'event' => 'acme.foo.action',
  18. 'method' => 'onFooAction',
  19. ]);
  20.  
  21. // registers an event subscriber
  22. $containerBuilder->register('subscriber_service_id', \AcmeSubscriber::class)
  23. ->addTag('kernel.event_subscriber');

By default, the listeners pass assumes that the event dispatcher's serviceid is event_dispatcher, that event listeners are tagged with thekernel.event_listener tag and that event subscribers are taggedwith the kernel.event_subscriber tag. You can change these defaultvalues by passing custom values to the constructor of RegisterListenersPass.

Creating and Dispatching an Event

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

Creating an Event Class

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

  1. namespace Acme\Store\Event;
  2.  
  3. use Acme\Store\Order;
  4. use Symfony\Contracts\EventDispatcher\Event;
  5.  
  6. /**
  7. * The order.placed event is dispatched each time an order is created
  8. * in the system.
  9. */
  10. class OrderPlacedEvent extends Event
  11. {
  12. public const NAME = 'order.placed';
  13.  
  14. protected $order;
  15.  
  16. public function __construct(Order $order)
  17. {
  18. $this->order = $order;
  19. }
  20.  
  21. public function getOrder()
  22. {
  23. return $this->order;
  24. }
  25. }

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

Note

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

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 nameof the event to dispatch and

  1. use Acme\Store\Event\OrderPlacedEvent;
  2. use Acme\Store\Order;
  3.  
  4. // the order is somehow created or retrieved
  5. $order = new Order();
  6. // ...
  7.  
  8. // creates the OrderPlacedEvent and dispatches it
  9. $event = new OrderPlacedEvent($order);
  10. $dispatcher->dispatch($event, OrderPlacedEvent::NAME);

Notice that the special OrderPlacedEvent object is created and passed tothe dispatch() method. Now, any listener to the order.placedevent will receive the OrderPlacedEvent.

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 andis notified each time those events are dispatched.

Another way to listen to events is via an event subscriber. An eventsubscriber is a PHP class that's able to tell the dispatcher exactly whichevents it should subscribe to. It implements theEventSubscriberInterfaceinterface, which requires a single static method calledgetSubscribedEvents().Take the following example of a subscriber that subscribes to thekernel.response and order.placed events:

  1. namespace Acme\Store\Event;
  2.  
  3. use Acme\Store\Event\OrderPlacedEvent;
  4. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  5. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  6. use Symfony\Component\HttpKernel\KernelEvents;
  7.  
  8. class StoreSubscriber implements EventSubscriberInterface
  9. {
  10. public static function getSubscribedEvents()
  11. {
  12. return [
  13. KernelEvents::RESPONSE => [
  14. ['onKernelResponsePre', 10],
  15. ['onKernelResponsePost', -10],
  16. ],
  17. OrderPlacedEvent::NAME => 'onStoreOrder',
  18. ];
  19. }
  20.  
  21. public function onKernelResponsePre(ResponseEvent $event)
  22. {
  23. // ...
  24. }
  25.  
  26. public function onKernelResponsePost(ResponseEvent $event)
  27. {
  28. // ...
  29. }
  30.  
  31. public function onStoreOrder(OrderPlacedEvent $event)
  32. {
  33. // ...
  34. }
  35. }

This is very similar to a listener class, except that the class itself cantell the dispatcher which events it should listen to. To register a subscriberwith the dispatcher, use theaddSubscriber()method:

  1. use Acme\Store\Event\StoreSubscriber;
  2. // ...
  3.  
  4. $subscriber = new StoreSubscriber();
  5. $dispatcher->addSubscriber($subscriber);

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

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

Stopping Event Flow/Propagation

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

  1. use Acme\Store\Event\OrderPlacedEvent;
  2.  
  3. public function onStoreOrder(OrderPlacedEvent $event)
  4. {
  5. // ...
  6.  
  7. $event->stopPropagation();
  8. }

Now, any listeners to order.placed that have not yet been called willnot be called.

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

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

EventDispatcher Aware Events and Listeners

The EventDispatcher always passes the dispatched event, the event'sname and a reference to itself to the listeners. This can lead to some advancedapplications of the EventDispatcher including dispatching other events insidelisteners, 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 thatis dispatched, are passed as arguments to the listener:

  1. use Symfony\Contracts\EventDispatcher\Event;
  2. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  3.  
  4. class Foo
  5. {
  6. public function myEventListener(Event $event, $eventName, EventDispatcherInterface $dispatcher)
  7. {
  8. // ... do something with the event name
  9. }
  10. }

Other Dispatchers

Besides the commonly used EventDispatcher, the component comeswith some other dispatchers:

Learn More