Skip to content

Commit 61ba3f2

Browse files
committed
[#1653] Taking the before and after filter chapter and dividing the before part and the after part
1 parent 2c54257 commit 61ba3f2

File tree

1 file changed

+117
-65
lines changed

1 file changed

+117
-65
lines changed

cookbook/event_dispatcher/before_after_filters.rst

Lines changed: 117 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ It is quite common in web application development to need some logic to be
88
executed just before or just after your controller actions acting as filters
99
or hooks.
1010

11-
In Symfony1, this was achieved with the preExecute and postExecute methods,
12-
most major frameworks have similar methods but there is no such thing in Symfony2.
11+
In symfony1, this was achieved with the preExecute and postExecute methods.
12+
Most major frameworks have similar methods but there is no such thing in Symfony2.
1313
The good news is that there is a much better way to interfere with the
1414
Request -> Response process using the :doc:`EventDispatcher component</components/event_dispatcher/introduction>`.
1515

@@ -27,24 +27,14 @@ token.
2727
.. note::
2828

2929
Please note that for simplicity in this recipe, tokens will be defined
30-
in config and neither database setup nor authentication via
31-
the Security component will be used.
30+
in config and neither database setup nor authentication via the Security
31+
component will be used.
3232

33-
Adding a hash to a JSON Response
34-
--------------------------------
33+
Before filters with the ``kernel.controller`` Event
34+
---------------------------------------------------
3535

36-
In the same context as the token example imagine that we want to add a sha1
37-
hash (with a salt using that token) to all our responses.
38-
39-
So, after generating a JSON response, this hash has to be added to the response.
40-
41-
Before and after filters with ``kernel.controller`` / ``kernel.response`` events
42-
--------------------------------------------------------------------------------
43-
44-
Basic Setup
45-
~~~~~~~~~~~
46-
47-
You can add basic token configuration using ``config.yml`` and the parameters key:
36+
First, store some basic token configuration using ``config.yml`` and the
37+
parameters key:
4838

4939
.. configuration-block::
5040

@@ -77,13 +67,9 @@ You can add basic token configuration using ``config.yml`` and the parameters ke
7767
Tag Controllers to be checked
7868
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7969

80-
A ``kernel.controller`` listener gets notified on every request, right before
81-
the controller is executed. Same happens to ``kernel.response`` listeners
82-
after the controller returns a Response object.
83-
84-
So, first, you need some way to identify if the controller that matches the
85-
request needs token validation. Regarding the response, you will also need
86-
some way to identify that the response needs to be hashed.
70+
A ``kernel.controller`` listener gets notified on *every* request, right before
71+
the controller is executed. So, first, you need some way to identify if the
72+
controller that matches the request needs token validation.
8773

8874
A clean and easy way is to create an empty interface and make the controllers
8975
implement it::
@@ -95,28 +81,19 @@ implement it::
9581
// ...
9682
}
9783

98-
And also create a ``HashedResponse`` object extending ``Response``
99-
100-
namespace Acme\DemoBundle\Response;
101-
102-
use Symfony\Component\HttpFoundation\Response;
103-
104-
class HashedResponse extends Response
105-
{
106-
// ...
107-
}
108-
10984
A controller that implements this interface simply looks like this::
11085

86+
namespace Acme\DemoBundle\Controller;
87+
11188
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
112-
use Acme\DemoBundle\Response\HashedResponse;
89+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
11390

114-
class FooController implements TokenAuthenticatedController
91+
class FooController extends Controller implements TokenAuthenticatedController
11592
{
116-
// An action that needs authentication and signature
93+
// An action that needs authentication
11794
public function barAction()
11895
{
119-
return new HashedResponse('Hello world');
96+
// ...
12097
}
12198
}
12299

@@ -127,16 +104,14 @@ Next, you'll need to create an event listener, which will hold the logic
127104
that you want executed before your controllers. If you're not familiar with
128105
event listeners, you can learn more about them at :doc:`/cookbook/service_container/event_listener`::
129106

130-
// src/Acme/DemoBundle/EventListener/BeforeListener.php
107+
// src/Acme/DemoBundle/EventListener/TokenListener.php
131108
namespace Acme\DemoBundle\EventListener;
132109

133110
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
134-
use Acme\DemoBundle\Response\HashedResponse;
135-
136111
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
137112
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
138113

139-
class BeforeAndAfterListener
114+
class TokenListener
140115
{
141116
private $tokens;
142117

@@ -158,40 +133,112 @@ event listeners, you can learn more about them at :doc:`/cookbook/service_contai
158133
}
159134

160135
if ($controller[0] instanceof TokenAuthenticatedController) {
161-
$token = $event->getRequest()->get('token');
136+
$token = $event->getRequest()->query->get('token');
162137
if (!in_array($token, $this->tokens)) {
163138
throw new AccessDeniedHttpException('This action needs a valid token!');
164139
}
165140
}
166141
}
142+
}
167143

168-
public function onKernelResponse(FilterResponseEvent $event)
169-
{
170-
$response = $event->getResponse();
144+
Registering the Listener
145+
~~~~~~~~~~~~~~~~~~~~~~~~
146+
147+
Finally, register your listener as a service and tag it as an event listener.
148+
By listening on ``kernel.controller``, you're telling Symfony that you want
149+
your listener to be called just before any controller is executed.
150+
151+
.. configuration-block::
152+
153+
.. code-block:: yaml
154+
155+
# app/config/config.yml (or inside your services.yml)
156+
services:
157+
demo.tokens.action_listener:
158+
class: Acme\DemoBundle\EventListener\BeforeAndAfterListener
159+
arguments: [ %tokens% ]
160+
tags:
161+
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
162+
163+
.. code-block:: xml
164+
165+
<!-- app/config/config.xml (or inside your services.xml) -->
166+
<service id="demo.tokens.action_listener" class="Acme\DemoBundle\EventListener\BeforeAndAfterListener">
167+
<argument>%tokens%</argument>
168+
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" />
169+
</service>
170+
171+
.. code-block:: php
172+
173+
// app/config/config.php (or inside your services.php)
174+
use Symfony\Component\DependencyInjection\Definition;
175+
176+
$listener = new Definition('Acme\DemoBundle\EventListener\BeforeAndAfterListener', array('%tokens%'));
177+
$listener->addTag('kernel.event_listener', array('event' => 'kernel.controller', 'method' => 'onKernelController'));
178+
$container->setDefinition('demo.tokens.action_listener', $listener);
179+
180+
With this configuration, your ``TokenListener`` ``onKernelController`` method
181+
will be executed on each request. If the controller that is about to be executed
182+
implements ``TokenAuthenticatedController``, token authentication is
183+
applied. This let's us have a "before" filter on any controller that you
184+
want.
185+
186+
After filters with the ``kernel.response`` Event
187+
------------------------------------------------
171188

172-
if ($response instanceof HashedResponse) {
173-
$token = $event->getRequest()->get('token');
189+
In addition to having a "hook" that's executed before your controller, you
190+
can also add a hook that's executed *after* your controller. For this example,
191+
imagine that you want to add a sha1 hash (with a salt using that token) to
192+
all responses that have passed this token authentication.
174193

175-
$hash = sha1($response->getContent().$token);
194+
Another core Symfony event - called ``kernel.response`` - is notified on
195+
every request, but after the controller returns a Response object. Creating
196+
an "after" listener is as easy as creating a listener class and registering
197+
it as a service on this event.
176198

177-
$response->setContent(
178-
json_encode(array(
179-
'hash' => $hash,
180-
'content' => $response->getContent(),
181-
))
182-
);
199+
For example, take the ``TokenListener`` from the previous example and first
200+
record the authentication token inside the request attributes. This will
201+
serve as a basic flag that this request underwent token authentication::
202+
203+
public function onKernelController(FilterControllerEvent $event)
204+
{
205+
// ...
206+
207+
if ($controller[0] instanceof TokenAuthenticatedController) {
208+
$token = $event->getRequest()->query->get('token');
209+
if (!in_array($token, $this->tokens)) {
210+
throw new AccessDeniedHttpException('This action needs a valid token!');
183211
}
212+
213+
// mark the request as having passed token authentication
214+
$$event->getRequest()->attributes->set('auth_token', $token);
184215
}
185216
}
186217

187-
Registering the Listener
188-
~~~~~~~~~~~~~~~~~~~~~~~~
218+
Now, add another method to this class - ``onKernelResponse`` - that looks
219+
for this flag on the request object and sets a custom header on the response
220+
if it's found::
189221

190-
Finally, register your listener as a service and tag it as an event listener.
191-
By listening on ``kernel.controller``, you're telling Symfony that you want
192-
your listener to be called just before any controller is executed. And by
193-
listening on ``kernel.response``, your listener will be called after returning
194-
a Response:
222+
// add the new use statement at the top of your file
223+
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
224+
225+
public function onKernelResponse(FilterResponseEvent $event)
226+
{
227+
// check to see if onKernelController marked this as a token "auth'ed" request
228+
if (!$token = $event->getRequest()->attributes->get('auth_token')) {
229+
return;
230+
}
231+
232+
$response = $event->getResponse();
233+
234+
// create a hash and set it as a response header
235+
$hash = sha1($response->getContent().$token);
236+
$response->headers->set('X-CONTENT-HASH', $hash);
237+
}
238+
239+
Finally, a second "tag" is needed on the service definition to notify Symfony
240+
that the ``onKernelResponse`` event should be notified for the ``kernel.response``
241+
event:
195242

196243
.. configuration-block::
197244

@@ -225,4 +272,9 @@ a Response:
225272
$listener->addTag('kernel.event_listener', array('event' => 'kernel.response', 'method' => 'onKernelResponse'));
226273
$container->setDefinition('demo.tokens.action_listener', $listener);
227274
228-
275+
That's it! The ``TokenListener`` is now notified before every controller is
276+
executed (``onKernelController``) and after every controller returns a response
277+
(``onKernelResponse``). By making specific controllers implement the ``TokenAuthenticatedController``
278+
interface, our listener knows which controllers it should take action on.
279+
And by storing a value in the request's "attributes" bag, the ``onKernelResponse``
280+
method knows to add the extra header. Have fun!

0 commit comments

Comments
 (0)