Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a cookbook section about event subscribers #5377

Merged
merged 9 commits into from
Oct 14, 2015
195 changes: 163 additions & 32 deletions cookbook/event_dispatcher/event_listener.rst
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
.. index::
single: Events; Create listener
single: Create subscriber

How to Create an Event Listener
===============================
How to Create Event Listeners and Subscribers
=============================================

Symfony has various events and hooks that can be used to trigger custom
behavior in your application. Those events are thrown by the HttpKernel
component and can be viewed in the :class:`Symfony\\Component\\HttpKernel\\KernelEvents` class.
During the execution of a Symfony application, lots of event notifications are
triggered. Your application can listen to these notifications and respond to
them by executing any piece of code.

To hook into an event and add your own custom logic, you have to create
a service that will act as an event listener on that event. In this entry,
you will create a service that will act as an exception listener, allowing
you to modify how exceptions are shown by your application. The ``KernelEvents::EXCEPTION``
event is just one of the core kernel events::
Internal events provided by Symfony itself are defined in the
:class:`Symfony\\Component\\HttpKernel\\KernelEvents` class. Third-party bundles
and libraries also trigger lots of events and your own application can trigger
:doc:`custom events </components/event_dispatcher/index>`.

// src/AppBundle/EventListener/AcmeExceptionListener.php
All the examples shown in this article use the same ``KernelEvents::EXCEPTION``
event for consistency purposes. In your own application, you can use any event
and even mix several of them in the same subscriber.

Creating an Event Listener
--------------------------

The most common way to listen to an event is to register an **event listener**::

// src/AppBundle/EventListener/ExceptionListener.php
namespace AppBundle\EventListener;

use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

class AcmeExceptionListener
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
Expand Down Expand Up @@ -57,12 +66,6 @@ event is just one of the core kernel events::
the ``kernel.exception`` event, it is :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`.
To see what type of object each event listener receives, see :class:`Symfony\\Component\\HttpKernel\\KernelEvents`.

.. note::

When setting a response for the ``kernel.request``, ``kernel.view`` or
``kernel.exception`` events, the propagation is stopped, so the lower
priority listeners on that event don't get called.

Now that the class is created, you just need to register it as a service and
notify Symfony that it is a "listener" on the ``kernel.exception`` event by
using a special "tag":
Expand All @@ -73,31 +76,145 @@ using a special "tag":

# app/config/services.yml
services:
kernel.listener.your_listener_name:
class: AppBundle\EventListener\AcmeExceptionListener
app.exception_listener:
class: AppBundle\EventListener\ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
- { name: kernel.event_listener, event: kernel.exception }

.. code-block:: xml

<!-- app/config/services.xml -->
<service id="kernel.listener.your_listener_name" class="AppBundle\EventListener\AcmeExceptionListener">
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
</service>
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="app.exception_listener"
class="AppBundle\EventListener\ExceptionListener">

<tag name="kernel.event_listener" event="kernel.exception" />
</service>
</services>
</container>

.. code-block:: php

// app/config/services.php
$container
->register('kernel.listener.your_listener_name', 'AppBundle\EventListener\AcmeExceptionListener')
->addTag('kernel.event_listener', array('event' => 'kernel.exception', 'method' => 'onKernelException'))
->register('app.exception_listener', 'AppBundle\EventListener\ExceptionListener')
->addTag('kernel.event_listener', array('event' => 'kernel.exception'))
;

.. note::

There is an additional tag option ``priority`` that is optional and defaults
to 0. The listeners will be executed in the order of their priority (highest to lowest).
This is useful when you need to guarantee that one listener is executed before another.
There is an optional tag attribute called ``method`` which defines which method
to execute when the event is triggered. By default the name of the method is
``on`` + "camel-cased event name". If the event is ``kernel.exception`` the
method executed by default is ``onKernelException()``.

The other optional tag attribute is called ``priority``, which defaults to
``0`` and it controls the order in which listeners are executed (the highest
the priority, the earlier a listener is executed). This is useful when you
need to guarantee that one listener is executed before another. The priorities
of the internal Symfony listeners usually range from ``-255`` to ``255`` but
your own listeners can use any positive or negative integer.

Creating an Event Subscriber
----------------------------

Another way to listen to events is via an **event subscriber**, which is a class
that defines one or more methods that listen to one or various events. The main
difference with the event listeners is that subscribers always know which events
they are listening to.

In a given subscriber, different methods can listen to the same event. The order
in which methods are executed is defined by the ``priority`` parameter of each
method (the higher the priority the earlier the method is called). To learn more
about event subscribers, read :doc:`/components/event_dispatcher/introduction`.

The following example shows an event subscriber that defines several methods which
listen to the same ``kernel.exception`` event::

// src/AppBundle/EventSubscriber/ExceptionSubscriber.php
namespace AppBundle\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

class ExceptionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
return array(
'kernel.exception' => array(
array('processException', 10),
array('logException', 0),
array('notifyException', -10),
)
);
}

public function processException(GetResponseForExceptionEvent $event)
{
// ...
}

public function logException(GetResponseForExceptionEvent $event)
{
// ...
}

public function notifyException(GetResponseForExceptionEvent $event)
{
// ...
}
}

Now, you just need to register the class as a service and add the
``kernel.event_subscriber`` tag to tell Symfony that this is an event subscriber:

.. configuration-block::

.. code-block:: yaml

# app/config/services.yml
services:
app.exception_subscriber:
class: AppBundle\EventSubscriber\ExceptionSubscriber
tags:
- { name: kernel.event_subscriber }

.. code-block:: xml

<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="app.exception_subscriber"
class="AppBundle\EventSubscriber\ExceptionSubscriber">

<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>

.. code-block:: php

// app/config/services.php
$container
->register(
'app.exception_subscriber',
'AppBundle\EventSubscriber\ExceptionSubscriber'
)
->addTag('kernel.event_subscriber')
;

Request Events, Checking Types
------------------------------
Expand All @@ -107,17 +224,18 @@ sub-requests), which is why when working with the ``KernelEvents::REQUEST``
event, you might need to check the type of the request. This can be easily
done as follow::

// src/AppBundle/EventListener/AcmeRequestListener.php
// src/AppBundle/EventListener/RequestListener.php
namespace AppBundle\EventListener;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class AcmeRequestListener
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) {
if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
// don't do anything if it's not the master request
return;
}
Expand All @@ -131,3 +249,16 @@ done as follow::
Two types of request are available in the :class:`Symfony\\Component\\HttpKernel\\HttpKernelInterface`
interface: ``HttpKernelInterface::MASTER_REQUEST`` and
``HttpKernelInterface::SUB_REQUEST``.

Events or Subscribers
---------------------

Listeners and subscribers can be used in the same application indistinctly. The
decision to use either of them is usually a matter of personal taste. However,
there are some minor advantages for each of them:

* **Subscribers are easier to reuse** because the knowledge of the events is kept
in the class rather than in the service definition. This is the reason why
Symfony uses subscribers internally;
* **Listeners are more flexible** because bundles can enable or disable each of
them conditionally depending on some configuration value.