mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-24 00:32:14 +01:00
488 lines
18 KiB
ReStructuredText
488 lines
18 KiB
ReStructuredText
The EventDispatcher Component
|
|
=============================
|
|
|
|
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 :doc:`the HttpKernel component </components/http_kernel>`.
|
|
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
|
|
------------
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/event-dispatcher
|
|
|
|
.. include:: /components/require_autoload.rst.inc
|
|
|
|
Usage
|
|
-----
|
|
|
|
.. seealso::
|
|
|
|
This article explains how to use the EventDispatcher features as an
|
|
independent component in any PHP application. Read the :doc:`/event_dispatcher`
|
|
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 :class:`Symfony\\Contracts\\EventDispatcher\\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
|
|
:ref:`event propagation <event_dispatcher-event-propagation>`, but not much
|
|
else.
|
|
|
|
.. seealso::
|
|
|
|
Read ":doc:`/components/event_dispatcher/generic_event`" 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
|
|
:class:`Symfony\\Component\\HttpKernel\\Event\\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::
|
|
|
|
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::
|
|
|
|
$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 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::
|
|
|
|
use Symfony\Contracts\EventDispatcher\Event;
|
|
|
|
$dispatcher->addListener('acme.foo.action', function (Event $event): void {
|
|
// 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::
|
|
|
|
use Symfony\Contracts\EventDispatcher\Event;
|
|
|
|
class AcmeListener
|
|
{
|
|
// ...
|
|
|
|
public function onFooAction(Event $event): void
|
|
{
|
|
// ... 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.
|
|
|
|
.. sidebar:: Registering Event Listeners and Subscribers in the Service Container
|
|
|
|
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::
|
|
|
|
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 you 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``::
|
|
|
|
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``.
|
|
|
|
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``.
|
|
|
|
.. _event_dispatcher-closures-as-listeners:
|
|
|
|
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-object:
|
|
|
|
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::
|
|
|
|
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 :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::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 optionally the
|
|
name of the event to dispatch. If it's not defined, the class of the ``Event``
|
|
instance will be used::
|
|
|
|
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
|
|
:class:`Symfony\\Contracts\\EventDispatcher\\Event` class. In such case,
|
|
you can document the event and its name in a generic ``StoreEvents`` class,
|
|
similar to the :class:`Symfony\\Component\\HttpKernel\\KernelEvents`
|
|
class::
|
|
|
|
namespace App\Event;
|
|
|
|
class StoreEvents {
|
|
|
|
/**
|
|
* @Event("Symfony\Contracts\EventDispatcher\Event")
|
|
*/
|
|
public const ORDER_PLACED = 'order.placed';
|
|
}
|
|
|
|
And use the :class:`Symfony\\Contracts\\EventDispatcher\\Event` class to
|
|
dispatch the event::
|
|
|
|
use Symfony\Contracts\EventDispatcher\Event;
|
|
|
|
$this->eventDispatcher->dispatch(new Event(), StoreEvents::ORDER_PLACED);
|
|
|
|
.. _event_dispatcher-using-event-subscribers:
|
|
|
|
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
|
|
:class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`
|
|
interface, which requires a single static method called
|
|
:method:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface::getSubscribedEvents`.
|
|
Take the following example of a subscriber that subscribes to the
|
|
``kernel.response`` and ``OrderPlacedEvent::class`` 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(): array
|
|
{
|
|
return [
|
|
KernelEvents::RESPONSE => [
|
|
['onKernelResponsePre', 10],
|
|
['onKernelResponsePost', -10],
|
|
],
|
|
OrderPlacedEvent::class => 'onPlacedOrder',
|
|
];
|
|
}
|
|
|
|
public function onKernelResponsePre(ResponseEvent $event): void
|
|
{
|
|
// ...
|
|
}
|
|
|
|
public function onKernelResponsePost(ResponseEvent $event): void
|
|
{
|
|
// ...
|
|
}
|
|
|
|
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
|
|
:method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::addSubscriber`
|
|
method::
|
|
|
|
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.
|
|
|
|
.. _event_dispatcher-event-propagation:
|
|
|
|
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
|
|
:method:`Symfony\\Contracts\\EventDispatcher\\Event::stopPropagation` method::
|
|
|
|
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
|
|
:method:`Symfony\\Contracts\\EventDispatcher\\Event::isPropagationStopped`
|
|
method which returns a boolean value::
|
|
|
|
// ...
|
|
$dispatcher->dispatch($event, 'foo.event');
|
|
if ($event->isPropagationStopped()) {
|
|
// ...
|
|
}
|
|
|
|
.. _event_dispatcher-dispatcher-aware-events:
|
|
|
|
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_dispatcher-event-name-introspection:
|
|
|
|
Event Name Introspection
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The ``EventDispatcher`` instance, as well as the name of the event that
|
|
is dispatched, are passed as arguments to the listener::
|
|
|
|
use Symfony\Contracts\EventDispatcher\Event;
|
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
|
|
|
class MyListener
|
|
{
|
|
public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher): void
|
|
{
|
|
// ... do something with the event name
|
|
}
|
|
}
|
|
|
|
Other Dispatchers
|
|
-----------------
|
|
|
|
Besides the commonly used ``EventDispatcher``, the component comes
|
|
with some other dispatchers:
|
|
|
|
* :doc:`/components/event_dispatcher/immutable_dispatcher`
|
|
* :doc:`/components/event_dispatcher/traceable_dispatcher`
|
|
|
|
Learn More
|
|
----------
|
|
|
|
* :doc:`/components/event_dispatcher/generic_event`
|
|
* :ref:`The kernel.event_listener tag <dic-tags-kernel-event-listener>`
|
|
* :ref:`The kernel.event_subscriber tag <dic-tags-kernel-event-subscriber>`
|
|
|
|
.. _Mediator: https://en.wikipedia.org/wiki/Mediator_pattern
|
|
.. _Observer: https://en.wikipedia.org/wiki/Observer_pattern
|
|
.. _Closures: https://www.php.net/manual/en/functions.anonymous.php
|
|
.. _PHP callable: https://www.php.net/manual/en/language.types.callable.php
|