Search code examples
eventssymfonytwig-extension

Embedded Twig controllers cause kernel.request events to fire multiple times


In my Symfony project, I've come across a bizarre problem with kernel.request event listeners firing multiple times when embedded Twig controllers are used.

In my custom event listener I have an event listener that sends a redirect response if a certain condition exists (an expired password in this case). In order to prevent a redirect loop I checked if we were already on the page:

if ($event->getRequest()->get('_route') != 'user_change_password') {
    $response = new RedirectResponse($this->router->generate('user_change_password'));
    $event->setResponse($response);
}

But that didn't stop the redirect loops. Until I added logging, I had no idea that an embedded controller would fire the kernel.request event (it's obvious in hindsight, since these embedded controllers work by sending a "sub-request"). I have a single embedded controller in by base twig template that checks for any alert messages and displays them.

Given the above, how can I

  1. be able to insert dynamic content into a base template (that all other templates extend), AND
  2. not have kernel.request event listeners fire multiple times.

Even though Symfony suggests inserting that dynamic content into base templates using embedded controllers, is this considered bad practice?

Would it be better to create a Twig extension to solve this problem? From what I've seen, Twig extensions are generally only used for simple stuff, like the price example in the cookbook, though I can't see why it wouldn't work for more complex, database-connected things. I'm just not sure on how to do that.

Examples are appreciated.


Possible related?: Symfony Controller executed multiple Time


Solution

  • You could do the redirection only if the event listener is executed for the master request:

    use Symfony\Component\HttpKernel\HttpKernelInterface;
    
    // ...
    
    if ($event->isMasterRequest() && $event->getRequest()->get('_route') != 'user_change_password') {
        $response = new RedirectResponse($this->router->generate('user_change_password'));
        $event->setResponse($response);
    }
    

    If you are still bound to Symfony 2.3, you can use the getRequestType() method compare its return value with the MASTER_REQUEST constant from the HttpKernelInterface (that's what isMasterRequest() does internally):

    use Symfony\Component\HttpKernel\HttpKernelInterface;
    
    // ...
    
    if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType() && $event->getRequest()->get('_route') != 'user_change_password') {
        $response = new RedirectResponse($this->router->generate('user_change_password'));
        $event->setResponse($response);
    }