Symfony2 twig mobile template fallback

2.5k views Asked by At

I need a simple way to fallback on a default template if no mobile version exists.

With some regular expressions I recognize mobile platforms and want to render a template with the following pattern:

<template_name>.mobile.html.twig

But if this template doesn't exist, I want it to automatically fallback on:

<template_name>.html.twig

which always exists.

I tried nearly all the answers from this post: Symfony 2 load different template depending on user agent properties but with no success. Unfortunately there are no version numbers referenced.

At the moment I am trying to copy and modify the default twig loader.

By the way, What I want to achieve with this is the possibility to deploy different templates for mobile devices by just adding a template of the same name and adding a .mobile.

UPDATE:

http://www.99bugs.com/handling-mobile-template-switching-in-symfony2/ This one is also a good approach. It modifies the format property of the request object which affects the automatic template guessing when you don't specify a template in the controller with the render function (or annotation) but just return an array.

Resulting template name:

view/<controller>/<action>.<request format>.<engine>

So you could switch the request format from html to mobile.html based on the device detection. The downside of this is that every template needs a mobile.html pendant (which then could just include the non-mobile version if not needed).

UPDATE:

Besides using a custom templating provider there is also the possibility to hook into the kernel.view event.

4

There are 4 answers

8
qooplmao On BEST ANSWER

You could create a service to handle it and then use it in the same way that you do the templating service like so..

Create a service with the templating and request service injected into it..

Service (YAML)

acme.templating: 
    class: Acme\AcmeBundle\Templating\TemplatingProvider
    scope: request
    arguments:
        - @templating
        - @request // I assume you use request in your platform decision logic,
                   // otherwise you don't needs this or the scope part
        - 'html' 

Class

class TemplatingProvider
{
    private $fallback;
    private $platform;
    ... __construct($templating, $request, $fallback) etc

    private function setPlatform() ... Your platform decision logic

    private function getPlatform()
    {
        if (null === $this->platform) {
            $this->setPlatform();
        }

        return $this->platform;
    }

    private function getTemplateName($name, $platform)
    {
        if ($platform === 'html') {
            return $name;
        }

        $template = explode('.', $name);

        $template = array_merge(
            array_slice($template, 0, -2),
            array($platform),
            array_slice($template, -2)
        );

        return implode('.', $template);
    }

    public function renderResponse($name, array $parameters = array())
    {
        $newname = $this->getTemplateName($name, $this->getPlatform());

        if ($this->templating->exists($newname)) {
            return $this->templating->render($newname);
        }

        return $this->templating->renderResponse($this->getTemplateName(
                                                $name, $this->fallback));
    }

And then you could just call your templating service instead of the current one..

return $this->container->get('acme.templating')
                          ->renderResponse('<template_name>.html.twig', array());
4
stefancarlton On

You can easily do this by harnessing the bundle inheritance properties in Symfony2 http://symfony.com/doc/current/cookbook/bundles/inheritance.html

  • create a bundle which holds your desktop templates (AcmeDemoDesktopBundle)
  • create a bundle which will hold your mobile templates (AcmeDemoMobileBundle) and mark the parent as AcmeDemoDesktopBundle

Then when you render a template simply call AcmeDemoMobileBundle:: - if the template exists, it'll be rendered otherwise you'll neatly fall back to the desktop version. No extra code, listeners or anything none-obvious required.

The downside of this of course is that you move your templates out of the individual bundles.

2
BENARD Patrick On

Can't you check if the template exist before ?

if ( $this->get('templating')->exists('<templatename>.html.twig') ) {
    //  return  this->render(yourtemplate)
} else {
    // return your default template
}

OR :

You can create a generic method, to insert in your root controller like :

public function renderMobile($templateName, $params)
{
    $templateShortName = explode('.html.twig', $templateName)[0];
    $mobileName = $templateShortName.'.mobile.html.twig';
    if ( $this->get('templating')->exists($mobileName) ) {

        return $this->renderView($mobileName, $params);
    } else {
        return $this->renderView($templateName, $params)
    }
}

with this you can do :

return $this->renderMobile('yourtemplate', [yourparams]);
0
asm89 On

The fallback behavior you describe isn't that easy to implement (we found out the hard way..). Good news is we wanted the same setup as you ask for and ended up using the LiipThemeBundle for this purpose. It allows you to have different "themes" based on for example a device. It will do the fallback part for you.

For example:

Rendering a template: @BundleName/Resources/template.html.twig

Will render and fallback to in order:

  • app/Resources/themes/phone/BundleName/template.html.twig
  • app/Resources/BundleName/views/template.html.twig
  • src/BundleName/Resources/themes/phone/template.html.twig
  • src/BundleName/Resources/views/template.html.twig

Edit: so with this approach you can have default templates that will always be the final fallback and have a special template for mobile where you need it.