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 anEvent
object thathas access to theResponse
object; - The dispatcher notifies (i.e. calls a method on) all listeners of the
kernel.response
event, allowing each of them to make modificationsto theResponse
object.
Installation
- $ 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 Event
object 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:
- use Symfony\Component\EventDispatcher\EventDispatcher;
- $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:
- $listener = new AcmeListener();
- $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 to
0
). 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:
- 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 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:
- 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 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:
- use Symfony\Component\DependencyInjection\ContainerBuilder;
- use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
- use Symfony\Component\DependencyInjection\Reference;
- use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
- use Symfony\Component\EventDispatcher\EventDispatcher;
- $containerBuilder = new ContainerBuilder(new ParameterBag());
- // register the compiler pass that handles the 'kernel.event_listener'
- // and 'kernel.event_subscriber' service tags
- $containerBuilder->addCompilerPass(new RegisterListenersPass());
- $containerBuilder->register('event_dispatcher', EventDispatcher::class);
- // registers an event listener
- $containerBuilder->register('listener_service_id', \AcmeListener::class)
- ->addTag('kernel.event_listener', [
- 'event' => 'acme.foo.action',
- 'method' => 'onFooAction',
- ]);
- // registers an event subscriber
- $containerBuilder->register('subscriber_service_id', \AcmeSubscriber::class)
- ->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:
- namespace Acme\Store\Event;
- use Acme\Store\Order;
- use Symfony\Contracts\EventDispatcher\Event;
- /**
- * The order.placed event is dispatched each time an order is created
- * in the system.
- */
- class OrderPlacedEvent extends Event
- {
- public const NAME = 'order.placed';
- protected $order;
- public function __construct(Order $order)
- {
- $this->order = $order;
- }
- public function getOrder()
- {
- return $this->order;
- }
- }
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 KernelEvents
class.
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
- 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, OrderPlacedEvent::NAME);
Notice that the special OrderPlacedEvent
object is created and passed tothe dispatch()
method. Now, any listener to the order.placed
event 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 theEventSubscriberInterface
interface, which requires a single static method calledgetSubscribedEvents()
.Take the following example of a subscriber that subscribes to thekernel.response
and order.placed
events:
- 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::NAME => 'onStoreOrder',
- ];
- }
- public function onKernelResponsePre(ResponseEvent $event)
- {
- // ...
- }
- public function onKernelResponsePost(ResponseEvent $event)
- {
- // ...
- }
- public function onStoreOrder(OrderPlacedEvent $event)
- {
- // ...
- }
- }
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:
- use Acme\Store\Event\StoreSubscriber;
- // ...
- $subscriber = new StoreSubscriber();
- $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:
- use Acme\Store\Event\OrderPlacedEvent;
- public function onStoreOrder(OrderPlacedEvent $event)
- {
- // ...
- $event->stopPropagation();
- }
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:
- // ...
- $dispatcher->dispatch($event, 'foo.event');
- if ($event->isPropagationStopped()) {
- // ...
- }
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:
- use Symfony\Contracts\EventDispatcher\Event;
- use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
- class Foo
- {
- public function myEventListener(Event $event, $eventName, EventDispatcherInterface $dispatcher)
- {
- // ... do something with the event name
- }
- }
Other Dispatchers
Besides the commonly used EventDispatcher
, the component comeswith some other dispatchers: