Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2: Change rendered view with a listener

I would like to render different views in different context in my Symfony2 project. I'm using multiple routes for the same actions and I would like to render a different page (view) but with the same controller. For example I have:

@Route("/articles/show", name="articles_show")
@Route("/mobile/articles/show", name="mobile_articles_show")

Both routes are using the same action : ArticlesController:showAction(), but should render 2 differents templates (for mobile users and regulars ones).

show.html.twig
mobile.show.html.twig

I do not want to use a if statement or whatever in my controller, so I created a listener (similar to a preExecute function)

Here is a part or my config.yml that defines my listener

services:
    controller.pre_execute_listener:
        class: MyProject\MyBundle\Listener\ControllerListener
        arguments: ["@security.context", "@doctrine", "@router", "@session"]
        tags:- { name: kernel.event_listener, event: kernel.controller, method: preExecute }

I was thinking about doing something like that in the listener preExecute function:

if(substr($route,0,7) == 'mobile_'){
    $view = 'mobile.'.$view;
}

Unfortunately I cannot find a way to get $view or update the view "on the fly", just before it's rendered.

I hope my question is clear enough, thanks in advance, any idea is welcome :)

J.

like image 951
Jonathan Hell Avatar asked Aug 20 '12 08:08

Jonathan Hell


2 Answers

Here is the solution:

First I have to listen to kernel.view, not kernel.controller.

Then I use the "@templating" service (Thanks Marko Jovanovic for the hint)

So here is my new config.yml:

services:
    controller.pre_execute_listener:
        class: MyProject\MyBundle\Listener\ControllerListener
        arguments: ["@templating"]
        tags:
                - { name: kernel.event_listener, event: kernel.view, method: preExecute }

Finally here is my listener preExecute function

public function preExecute(\Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event){
    //result returned by the controller
    $data = $event->getControllerResult();

    /* @var $request  \Symfony\Component\HttpFoundation\Request */
    $request =  $event->getRequest();       
    $template = $request->get('_template');
    $route = $request->get('_route');

    if(substr($route,0,7) == 'mobile_'){
        $newTemplate = str_replace('html.twig','mobile.html.twig',$template);

        //Overwrite original template with the mobile one
        $response = $this->templating->renderResponse($newTemplate, $data);
        $event->setResponse($response);
    }
}

Hope this helps!

J.

like image 111
Jonathan Hell Avatar answered Sep 29 '22 15:09

Jonathan Hell


Worth noting: The accepted solution doesn't actually work if you directly return a Response-object (e.g. when you call $this->render()), because the kernel.view event is not fired in that case:

If the controller doesn't return a Response object, then the kernel dispatches another event - kernel.view.

— see Symfony's HTTP Kernel Docs

I couldn't work out a way around this, but found another interesting solution for the same problem: You could simply extend twig's render engine like the ZenstruckMobileBundle does or write your own file locator like the LiipThemeBundle.

[edit:] Alternatively you could also override the TemplateNameParser.

like image 30
Iris Schaffer Avatar answered Sep 29 '22 16:09

Iris Schaffer