New in Symfony 2.7: Twig as a First-Class Citizen

When I started to work on Symfony2, Twig didn't exist. Anyway, to ease using PHP as a templating engine, I created the Symfony Templating Component.

Later on, not very satisfied with using PHP as a templating language, I decided to create a new templating language, Twig, based on the Python Jinja2 language. And Symfony2 became the first popular framework to adopt a non-PHP templating engine in core. Of course, I had no idea if it would become a popular choice amongst Symfony developers, and so Symfony2 lets you use both PHP and Twig in your applications.

Fast forward to 2015; it's clear that Twig won the heart of Symfony2 developers and the heart of many PHP developers; Twig is now also used by many Open-Source CMSes besides Symfony.

But what would Twig as a First-Class Citizen mean in Symfony2 then? To be able to support PHP and Twig in Symfony, we added an abstraction layer. As a developer, you mostly interact with this abstraction via the templating service; that allows one application to use PHP and Twig. As you can imagine, this abstraction adds a layer of complexity and it comes with some performance impact.

For Symfony 3.0, I'd like to extract the Templating Component into an independent library (for the few people using PHP with Symfony) but I'd also like for Twig to be front and center in the framework. The good news is that most of the work has already been done in Symfony 2.7.

Using Twig in Symfony 2.6

In Symfony 2.6, you cannot use Twig "directly"; you must instead use the templating sub-system via the templating service. Moreover, as some Twig extensions depends on PHP helpers, the PHP templating system is always loaded even if not configured in the templating.engines option.

So, the minimal code you can write to render a Twig template reads as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

require_once __DIR__.'/../autoload.php.dist';

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;

class SimpleKernel extends Kernel
{
    public function registerBundles()
    {
        return array(new FrameworkBundle(), new TwigBundle());
    }

    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load(function ($container) {
            $container->loadFromExtension('framework', array(
                'secret' => '$ecret',
                'router' => array('resource' => ''),
                'templating' => array('engines' => array('twig')),
            ));
        });
    }
}

$kernel = new SimpleKernel('prod', false);
$kernel->boot();

$c = $kernel->getContainer();
$c->get('request_stack')->push(Request::create('/'));

echo $c->get('templating')->render('index.html.twig');

As you can see, the Twig bundle has been enabled and the templating.engines sets Twig as the only engine.

Using Twig without the Templating sub-system

As of Symfony 2.7, the templating configuration can be removed altogether and Twig can be used directly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

require_once __DIR__.'/../autoload.php.dist';

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;

class SimpleKernel extends Kernel
{
    public function registerBundles()
    {
        return array(new FrameworkBundle(), new TwigBundle());
    }

    public function registerContainerConfiguration(LoaderInterface $loader)
    {
        $loader->load(function ($container) {
            $container->loadFromExtension('framework', array(
                'secret' => '$ecret',
            ));
        });
    }
}

$kernel = new SimpleKernel('prod', false);
$kernel->boot();

$c = $kernel->getContainer();
$c->get('request_stack')->push(Request::create('/'));

echo $c->get('twig')->render('index.html.twig');

Using Twig directly has some drawbacks: you cannot use some base controller class shortcuts (like render() or renderResponse()), the web profiler does not display the time it took to render templates anymore, the "@bundle" notation cannot be used when referencing templates, and some other smaller things. Luckily, all those issues will be fixed in the coming weeks.

What about performance?

http://symfony.com/uploads/assets/symfony-twig-performance.png

Not calling an abstraction layer simplifies the code you have to understand and it allows to easily use all native Twig capabilities. But there is another nice side-effect. As you probably know, loading code in PHP (even with an opcode cache) induces a performance penalty; the less code you have, the most performant your app is.

Using Twig directly, without enabling the templating sub-system allows for a nice performance boost as demonstrated by this Blackfire.io profile comparison.

If you have a look at the "metrics" tab, you will see that the number of classes loaded for this simple example drops from 103 to 55; that's 48 less classes to load in the new code. Not loading the classes also means less memory consumption: it drops by 30%.

Also, in the call graph, you won't see any nodes for the various default Twig extensions as the time it takes to load them is negligible... except for one: the Fragment extension. Spotting this anomaly was easy when I profiled the code, and after digging into the problem more, I made a change to lazy-load the fragment renderers. That will also give a nice performance boost in Symfony 2.7.

Comments

30% less memory is awesome! Congratulations! =) Can't wait for 3.0!
> the "@bundle" notation cannot be used when referencing templates, and some other smaller things

this is wrong. This notation is the Twig native notation (and in earlier versions of Symfony, the case where it was impossible to use it was actually when using the templating engine with multiple engines enabled)
The issue might be related for the Bundle:Foo:test.html.twig notation, not to the @Bundle notation

@Hugo in real world, the memory consumption gain will be much smaller (because a typical Symfony request is likely to use much more stuff than this small snippet)
Great news!! Hope I can find some time soon to discuss the inline optimization of the "route" and "trans" twig filters.
Lovely news.

Thanks Fabien and the Symfony Team.

Scott
I am probably wrong but in 2.6 I inject @twig in my controllers, isn't it the same?
If you are a melody user (http://melody.sensiolabs.org/), you can run the following gist (https://gist.github.com/lyrixx/5a807d97c77c496e871a):

sudo melody self-update # be sure to use the very last melody release
melody run -vvv 5a807d97c77c496e871a your-name-here

;)
Awesome! But one thing isn't exactly clear to me. The 2.7 Twig helpers will not rely on the PHP engine anymore, so will not be loaded for 2.7? This is clear for using Twig without the templating sub-system, but would be great if this is also true for 2.7 itself :)
Nice decision!

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.