Rendering templates from a Twig extension

Published

Given you need to render something - a toolbar for example - inside your template in a Symfony application. You have some options to do this like creating a specific controller action. Embedding a controller action is already described thoroughly in the Symfony docs and seems to be the most legit answer on this topic in multiple StackOverflow questions. But there is another option; creating a Twig extension which facilitates a function to render your template. How to create such extension you ask? In this blog post I'll explain how.

The first thing you need to do is create an extension, duh! So let's assume we have the well-known AcmeDemoBundle initialized in our Symfony project. As an example we create a Twig extension class (which is also described here) who renders the HTML of a toolbar;

php
<?php
// src/Acme/DemoBundle/Twig/AcmeExtension.php
namespace Acme\DemoBundle\Twig;
class AcmeExtension extends \Twig_Extension
{
public function getFilters()
{
new \Twig_SimpleFunction('toolbar', array($this, 'renderToolbar'), array(
'is_safe' => array('html')
)),
}
public function renderToolbar()
{
$toolbar = <<<EOF
<ul class="toolbar">
<li class="toolbar-item"><a href="#">Tool #1</a>
<li class="toolbar-item"><a href="#">Tool #2</a>
</ul>
EOF;
return $toolbar;
}
public function getName()
{
return 'acme_extension';
}
}

You see the returned HTML of the toolbar? I know, not the best example. But you get the idea. You probably also noticed the very ugly HTML string in the renderToolbar method.

Does this have some room for improvement? Absolutely! Let's move the HTML string to a separate template which looks like the following;

twig
<ul class="toolbar">
<li class="toolbar-item"><a href="#">Tool #1</a>
<li class="toolbar-item"><a href="#">Tool #2</a>
</ul>

This template can be used as AcmeDemoBundle::toolbar.html.twig and needs to be rendered from our extension. The first thing that probably pops up into your mind is to inject the templating or twig service. A very valid solution indeed, but it can be achieved much easier. You can let Twig inject the current Twig environment into your renderToolbar method as argument. You can see that in the example below;

php
<?php
// src/Acme/DemoBundle/Twig/AcmeExtension.php
namespace Acme\DemoBundle\Twig;
class AcmeExtension extends \Twig_Extension
{
public function getFilters()
{
new \Twig_SimpleFunction('toolbar', array($this, 'renderToolbar'), array(
'is_safe' => array('html'),
'needs_environment' => true
)),
}
public function renderToolbar(\Twig_Environment $twig)
{
return $twig->render('AcmeDemoBundle::toolbar.html.twig');
}
public function getName()
{
return 'acme_extension';
}
}

Please notice the addition of 'needs_environment' => true, this tells Twig to inject the current environment. And because it's a Symfony enviroment, you can use your bundle's template shortcut like you normally do.