@@ -8,8 +8,8 @@ It is quite common in web application development to need some logic to be
8
8
executed just before or just after your controller actions acting as filters
9
9
or hooks.
10
10
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.
13
13
The good news is that there is a much better way to interfere with the
14
14
Request -> Response process using the :doc: `EventDispatcher component</components/event_dispatcher/introduction> `.
15
15
@@ -27,24 +27,14 @@ token.
27
27
.. note ::
28
28
29
29
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.
32
32
33
- Adding a hash to a JSON Response
34
- --------------------------------
33
+ Before filters with the `` kernel.controller `` Event
34
+ ---------------------------------------------------
35
35
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:
48
38
49
39
.. configuration-block ::
50
40
@@ -77,13 +67,9 @@ You can add basic token configuration using ``config.yml`` and the parameters ke
77
67
Tag Controllers to be checked
78
68
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79
69
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.
87
73
88
74
A clean and easy way is to create an empty interface and make the controllers
89
75
implement it::
@@ -95,28 +81,19 @@ implement it::
95
81
// ...
96
82
}
97
83
98
- And also create a ``HashedResponse `` object extending ``Response ``
99
-
100
- namespace Acme\D emoBundle\R esponse;
101
-
102
- use Symfony\C omponent\H ttpFoundation\R esponse;
103
-
104
- class HashedResponse extends Response
105
- {
106
- // ...
107
- }
108
-
109
84
A controller that implements this interface simply looks like this::
110
85
86
+ namespace Acme\DemoBundle\Controller;
87
+
111
88
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
112
- use Acme\DemoBundle\Response\HashedResponse ;
89
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller ;
113
90
114
- class FooController implements TokenAuthenticatedController
91
+ class FooController extends Controller implements TokenAuthenticatedController
115
92
{
116
- // An action that needs authentication and signature
93
+ // An action that needs authentication
117
94
public function barAction()
118
95
{
119
- return new HashedResponse('Hello world');
96
+ // ...
120
97
}
121
98
}
122
99
@@ -127,16 +104,14 @@ Next, you'll need to create an event listener, which will hold the logic
127
104
that you want executed before your controllers. If you're not familiar with
128
105
event listeners, you can learn more about them at :doc: `/cookbook/service_container/event_listener `::
129
106
130
- // src/Acme/DemoBundle/EventListener/BeforeListener .php
107
+ // src/Acme/DemoBundle/EventListener/TokenListener .php
131
108
namespace Acme\DemoBundle\EventListener;
132
109
133
110
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
134
- use Acme\DemoBundle\Response\HashedResponse;
135
-
136
111
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
137
112
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
138
113
139
- class BeforeAndAfterListener
114
+ class TokenListener
140
115
{
141
116
private $tokens;
142
117
@@ -158,40 +133,112 @@ event listeners, you can learn more about them at :doc:`/cookbook/service_contai
158
133
}
159
134
160
135
if ($controller[0] instanceof TokenAuthenticatedController) {
161
- $token = $event->getRequest()->get('token');
136
+ $token = $event->getRequest()->query-> get('token');
162
137
if (!in_array($token, $this->tokens)) {
163
138
throw new AccessDeniedHttpException('This action needs a valid token!');
164
139
}
165
140
}
166
141
}
142
+ }
167
143
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
+ ------------------------------------------------
171
188
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.
174
193
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.
176
198
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!');
183
211
}
212
+
213
+ // mark the request as having passed token authentication
214
+ $$event->getRequest()->attributes->set('auth_token', $token);
184
215
}
185
216
}
186
217
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::
189
221
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:
195
242
196
243
.. configuration-block ::
197
244
@@ -225,4 +272,9 @@ a Response:
225
272
$listener->addTag('kernel.event_listener', array('event' => 'kernel.response', 'method' => 'onKernelResponse'));
226
273
$container->setDefinition('demo.tokens.action_listener', $listener);
227
274
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