There's a much more detailed chapter about this subject in my book A Year With Symfony.

Symfony2 provides multiple ways of blocking, providing or modifying the response. You can:

  1. Intercept each request by listening to the kernel.request event and set the response directly on the event (which will effectively skip execution of a controller)

  2. Modify the controller or its arguments by listening to the kernel.controller event, then calling setController on the event object and modifying the attributes of the Request object.

  3. Change the response rendered by a controller, by listening to the kernel.response event.

I have struggled many times with providing an answer to the following seemingly easy question: what if I want to wait until just before the controller gets called, then do some checks and prevent the user from executing the current controller, and instead, render an entirely different response. How should I proceed? At first thought, using option two would be good. But not really:

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class ControllerListener
{
    public function onFilterController(FilterControllerEvent $event)
    {
        // do some checks

        $response = new Response('...');

        // ... no way to set the response right now
    }
}

Option 3 would also be a bad option, since changing the response would be too late: the controller is already executed, so all heavy-weight processes have finished. It does not make sense to completely change the response this late in the process.

More drastic would be to choose option 1: listen to every request and return a custom response when needed. In some cases this could be just fine (though a bit "too much" I think), but in some other cases (the ones I am thinking of) setting the response as a reaction to the kernel.request event is too early. We need everything to be in place, before running our controller-specific checks. This way all the possible execution paths have been narrowed down to just one: we know what action the user wishes to do and we are assured he has the rights to do so, based on authentication and authorization.

The inspiration for the solution I found came from learning about the way the Security Component redirects users. It throws an exception, which will be caught by the default exception handler, which dispatches a kernel.exception event. Each listener is allowed to set a response. For instance a RedirectResponse to redirect the user to the login page. But in fact, exception handlers could return any response they want:

use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;

class ExceptionListener
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // totally custom response
        $response = new Response('...');

        $event->setResponse($response);
    }
}

Since most of the times preventing the user from executing a controller, would be something of an exception (the normal workflow being simply the unhindered execution of the controller) it does make sense to use the "throw exception-set response" workflow in these kind of situations. This means: we should have a controller listener, listening to the kernel.controller event, which does some "just-in-time" checks to verify that the user is allowed to execute the controller. When he is not, the listener should throw a specific type of exception, something like a ControllerNotAvailableException. By extending the exception we could give it all kinds of attributes for later reference, but the exception message itself could also simply describe why the controller was not available for the user.

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class ControllerListener
{
    public function onFilterController(FilterControllerEvent $event)
    {
        if (...) {
            // the user is allowed to be execute the controller, so do nothing
            return;
        }

        // the user is not allowed to be here, so
        throw new ControllerNotAvailableException('You are not allowed to execute this controller');
    }
}

On the other end we have the exception listener, which would of course only respond when the exception is of the specified type:

use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;

class ExceptionListener
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        $exception = $event->getException();

        if (!($exception instanceof ControllerNotAvailableException)) {
            return;
        }

        $response = new Response($exception->getMessage());

        $event->setResponse($response);
    }
}

Don't forget to hook this all up (see the relevant cookbook article on creating event listeners) using service definitions and kernel.event_listener tags.

Making a subrequest in the exception handler

Though you may prefer to generate the Response object by hand, in most cases you would be happier to work with another controller, some Twig template, etc. This can be done by letting the kernel make a subrequest. You don't have to inject the HttpKernel as a service, since you can retrieve it by calling the getKernel() method on the GetResponseForExceptionEvent object. Then you can make the appropriate subrequest, for instance render a page showing the user some options for upgrading his account from free to premium, etc.:

class ExceptionListener
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // ...

        $kernel = $event->getKernel();

        /** @var \Symfony\Bundle\FrameworkBundle\HttpKernel $kernel */

        // $exception will be available as a controller argument
        $response  = $kernel->forward('MatthiasAccountBundle:Account:upgrade', array(
            'exception' => $exception,
        ));

        $event->setResponse($response); // this will stop event propagation
    }
}

You may also immediately call a controller, instead of letting the kernel nicely handle the forwarding:

use Symfony\Component\HttpKernel\HttpKernelInterface;

class ExceptionListener
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // ...

        $attributes = array(
            '_controller' => 'MatthiasAccountBundle:Account:upgrade',
        );
        $request = $event->getRequest()->duplicate(null, null, $attributes);
        $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST, false);
    }
}

Make something nice of it: use annotations

It is very well possible to use annotations to mark controllers that should be checked specifically. It would be nice to have something like:

use Matthias\CreditBundle\Annotation\RequiresCredits;

class SomeController
{
    /**
     * @RequiresCredits(100)
     */
    public function buyShieldAction()
    {
        // only executable when the current user has at least 100 credits
    }
}

As can be read in full detail in the Doctrine Common documentation, creating annotations is very easy. Just create a class, and mark it as an annotation:

namespace Matthias\CreditBundle\Annotation;

use Doctrine\Common\Annotations\Annotation;

class RequiresCredits extends Annotation
{
}

Make sure the controller listener gets the annotation_reader injected. Now you are ready to read the metadata specified by the controller annotation:

use Doctrine\Common\Annotations\Reader;
use Matthias\CreditBundle\Annotation\RequiresCredits;
use Doctrine\Common\Util\ClassUtils;

class ControllerListener
{
    private $annotationReader;

    public function __construct(Reader $annotationReader)
    {
        $this->annotationReader = $annotationReader;
    }

    public function onFilterController(FilterControllerEvent $event)
    {
        $controller = $event->getController();

        list($object, $method) = $controller;

        // the controller could be a proxy, e.g. when using the JMSSecuriyExtraBundle or JMSDiExtraBundle
        $className = ClassUtils::getClass($object);

        $reflectionClass = new \ReflectionClass($className);
        $reflectionMethod = $reflectionClass->getMethod($method);

        $allAnnotations = $this->annotationReader->getMethodAnnotations($reflectionMethod);

        $creditAnnotations = array_filter($allAnnotations, function($annotation) {
            return $annotation instanceof RequiresCredits;
        });

        foreach ($creditAnnotations as $creditAnnotation) {
            $requiredAmountOfCredits = $creditAnnotation->value;

            // ... verify if the user has the required amount

            // throw an exception, catch it using an exception listener (see above)
            // then in the exception listener, create a RedirectResponse or something 
            // and set it on the $event object
        }
    }
}

As you can imagine, using annotations will make your controller code much cleaner, since all pre-execute logic you would normally put inside your controller, can be moved to the docblock before the controller.

PHP Symfony2 Security annotations controller events