How to write host-aware Twig templates in Symfony

There is an easy way in Symfony to inject a global variable into every Twig template in an application, which is described here.
However there are some cases where you don’t what to inject a simple static variable, but something dynamic like the hostname of the current request (or even the request object itself). If you want to do something like this it’s a bit more complicated. Basically you create a new Twig extension and register it with the application.

After I wrote the article I noticed that there is a much easier way to get the hostname in Twig templates. You can access the request object with {{ app.request }} and the hostname with {{ app.request.host }}. Nevertheless the tutorial shows how to inject other global variables :)

Step 1: Create the extension
The first thing you have to do is to write a Twig extension. In order to to this you have to create a new class which inherits Twig_Extension. The only method you have to provide by convention is “getName”. Let’s say a bundle’s source code it located at “src/MyName/MyBundle”. Then you could create the file “src/MyName/MyBundle/Twig/Extension/HostnameExtension.php”. It’s not neccessary to create the subfolders also, but it helps to structure your project:

// src/MyName/MyBundle/Twig/Extension/HostnameExtension.php
namespace MyName\MyBundle\Twig\Extension;
 
use \Twig_Extension;
 
class HostnameExtension extends Twig_Extension
{
    public function getName() 
    {
        return 'myname.hostnameExtension';
    }
}


The name returned by “getName” isn’t neccessary in this tutorial, but you have to provide a name. This would be a completely functional Twig extension, but it hasn’t any functionality. If we want to get the hostname, we need access to the request object. And in order to get access to the request object we have to get an instance of the Service Container. This is the place from where you can access all the services of a Symfony application.
So we add a constructor which accepts such a container as a parameter:

...
use Symfony\Component\DependencyInjection\ContainerInterface;
...
 
class HostExtension extends Twig_Extension
{
    protected $container;
 
    public function __construct(ContainerInterface $container) 
    {
        $this->container = $container;
    }
    ...
}

How the container is passed to the class and how the class is intantiated will be covered later. At the moment we just assume that it’s possible somehow.
So now we have the container and can improve the Twig extension. You can define the method “getGlobals” which must return an associative array. This array is a hash table for the names and values of global Twig variables. In our case we want to define a global variable which is called “hostname”, so we need the following implementation:

...
use Symfony\Component\DependencyInjection\ContainerInterface;
...
 
class HostExtension extends Twig_Extension
{
    ...
    public function getGlobals() 
    {
        // Retrieve the Request object form the container and get the hostname
        $hostname = $this->container->get('request')->getHost();
        return array('hostname' => $hostname);
    }
    ...
}

The Twig extension is complete now. It will setup a global variable called “hostname” which contains the hostname from the current request to the website.

Step 2: Create the service definition
Now we have to tell Symfony how to use the class we just created. In order to do this we have to add a new service definition to the services.xml which is located in src/Resources/config.

...
    <services>
        ...
        <service id="myname.mybundle.twigHostnameExtension" class="MyName\MyBundle\Twig\Extension\HostnameExtension">
            <tag name="twig.extension" />
            <argument type="service" id="service_container" />
        </service>
        ...
    </services>
...

This definition tells the system to load the class “MyName\MyBundle\Twig\Extension\HostnameExtension” as a service and pass the service with the id “service_container” as the first argument to the constructor. It also says that our service is a Twig extension and should be loaded by Twig as such.

Step 3: Let the system look into services.xml
Maybe this step is not neccessary for you. But especially if you work with a completely fresh bundle you should look into “MyName\MyBundle\DependencyInjection\MyNameMyBundleExtension” which is the entry point to the Symfony system of the bundle. The file should look like this:

// src/MyName/MyBundle/DependencyInjection/MyNameMyBundleExtension.php
namespace MyName\MyBundle\DependencyInjection;
 
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
 
class MyNameMyBundleExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.xml');
    }
}

This tells Symfony to parse “services.xml” when the Bundle “MyNameMyBundle” is loaded.

Step 3: Use the variable in your templates
Now you can use the “hostname” variable in your Twig templates. This makes it easy to give one look and style to “example1.com” and another to “example2.com”. A template could look like this:

...
<head>
    {{ include "MyName:MyBundle:styling." ~ hostname ~ ".twig.html" }}
</head>
...
{% if hostname === "example1.com" %}
    <h1>Welcome on example1.com!</h1>
{% else %}
    <h1>Welcome on example2.com!</h1>
{% endif %}

3 thoughts on “How to write host-aware Twig templates in Symfony

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">