How to Set Up Before and After Filters

It is quite common in web application development to need some logic to be
executed just before or just after your controller actions acting as filters
or hooks.

Some web frameworks define methods like preExecute() and postExecute(),
but there is no such thing in Symfony. The good news is that there is a much
better way to interfere with the Request -> Response process using the
EventDispatcher component.

Imagine that you need to develop an API where some controllers are public
but some others are restricted to one or some clients. For these private features,
you might provide a token to your clients to identify themselves.

So, before executing your controller action, you need to check if the action
is restricted or not. If it is restricted, you need to validate the provided
token.

Note

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

A kernel.controller listener gets notified on every request, right before
the controller is executed. So, first, you need some way to identify if the
controller that matches the request needs token validation.

A clean and easy way is to create an empty interface and make the controllers
implement it:

Next, you'll need to create an event listener, which will hold the logic
that you want to be executed before your controllers. If you're not familiar with
event listeners, you can learn more about them at Events and Event Listeners:

// src/AppBundle/EventListener/TokenListener.phpnamespaceAppBundle\EventListener;useAppBundle\Controller\TokenAuthenticatedController;useSymfony\Component\HttpKernel\Exception\AccessDeniedHttpException;useSymfony\Component\HttpKernel\Event\FilterControllerEvent;classTokenListener{private$tokens;publicfunction__construct($tokens){$this->tokens=$tokens;}publicfunctiononKernelController(FilterControllerEvent$event){$controller=$event->getController();/* * $controller passed can be either a class or a Closure. * This is not usual in Symfony but it may happen. * If it is a class, it comes in array format */if(!is_array($controller)){return;}if($controller[0]instanceofTokenAuthenticatedController){$token=$event->getRequest()->query->get('token');if(!in_array($token,$this->tokens)){thrownewAccessDeniedHttpException('This action needs a valid token!');}}}}

Finally, register your listener as a service and tag it as an event listener.
By listening on kernel.controller, you're telling Symfony that you want
your listener to be called just before any controller is executed.

With this configuration, your TokenListeneronKernelController() method
will be executed on each request. If the controller that is about to be executed
implements TokenAuthenticatedController, token authentication is
applied. This lets you have a "before" filter on any controller that you
want.

In addition to having a "hook" that's executed before your controller, you
can also add a hook that's executed after your controller. For this example,
imagine that you want to add a sha1 hash (with a salt using that token) to
all responses that have passed this token authentication.

Another core Symfony event - called kernel.response - is notified on
every request, but after the controller returns a Response object. Creating
an "after" listener is as easy as creating a listener class and registering
it as a service on this event.

For example, take the TokenListener from the previous example and first
record the authentication token inside the request attributes. This will
serve as a basic flag that this request underwent token authentication:

Now, add another method to this class - onKernelResponse() - that looks
for this flag on the request object and sets a custom header on the response
if it's found:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// add the new use statement at the top of your fileuseSymfony\Component\HttpKernel\Event\FilterResponseEvent;publicfunctiononKernelResponse(FilterResponseEvent$event){// check to see if onKernelController marked this as a token "auth'ed" requestif(!$token=$event->getRequest()->attributes->get('auth_token')){return;}$response=$event->getResponse();// create a hash and set it as a response header$hash=sha1($response->getContent().$token);$response->headers->set('X-CONTENT-HASH',$hash);}

Finally, a second "tag" is needed in the service definition to notify Symfony
that the onKernelResponse event should be notified for the kernel.response
event:

That's it! The TokenListener is now notified before every controller is
executed (onKernelController()) and after every controller returns a response
(onKernelResponse()). By making specific controllers implement the TokenAuthenticatedController
interface, your listener knows which controllers it should take action on.
And by storing a value in the request's "attributes" bag, the onKernelResponse()
method knows to add the extra header. Have fun!