Twig – the Most Popular Stand-Alone PHP Template Engine

Claudio Ribeiro
Share

This article was peer reviewed by Wern Ancheta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


Twig is a template engine for PHP. But isn’t PHP itself a template engine?

Yes and no!

Even though PHP started as a template engine, it didn’t evolve like one, and while we can still use it as one please tell me which version of “Hello world” you prefer:

<?php echo "<p> Hello " . $name . "</p>"; ?>

or

<p> Hello {{ name }} </p>

PHP is a verbose language, and that verbosity is amplified when trying to output HTML content.

Modern template systems will take away some of that verbosity and still add a fair share of functionality on top. Things like security and debug features are mainstays in modern template engines.

Today, we’re focusing on Twig.

Twig logo

Twig is a template engine created by Sensio labs, the company behind Blackfire and Symfony. Let’s take a look at its main strengths and how can we use it in our projects.

Installation

There are two ways of installing Twig. We can use the tar ball available on their website, or we can use Composer, just like we always do and recommend.

composer require twig/twig

We’re assuming you’re running an environment with PHP set up and Composer globally installed. Your best bet is using Homestead Improved – it’ll let you get started in 5 minutes on the exact same machine we’re using, so we’re all on the same page. If you’d like to learn more about PHP Environments, we have an excellent premium book about that available for purchase here.

Before we go any further, there’s something we need to clarify first.

As a template engine, Twig operates both on the front and on the back end of a project. Because of that, we can look at Twig in two different ways: Twig for template designers and Twig for developers.

On one side, we prepare all data we need. On the other side, we render all that data.

Basic Usage

To exemplify the basic usage of Twig, let’s create a simple project. First of all, we need to bootstrap Twig. Let’s create a bootstrap.php file with the following content:

<?php

// Load our autoloader
require_once __DIR__.'/vendor/autoload.php';

// Specify our Twig templates location
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');

 // Instantiate our Twig
$twig = new Twig_Environment($loader);

Twig uses a central object called Environment. Instances of this class are used to store the configuration, extensions, and to load templates from the file system or other locations.

With our Twig instance bootstrapped, we can go on and create an index.php file where we will load some data and pass it to a Twig template.

<?php

require_once __DIR__.'/bootstrap.php';

// Create a product list
$products = [
    [
        'name'          => 'Notebook',
        'description'   => 'Core i7',
        'value'         =>  800.00,
        'date_register' => '2017-06-22',
    ],
    [
        'name'          => 'Mouse',
        'description'   => 'Razer',
        'value'         =>  125.00,
        'date_register' => '2017-10-25',
    ],
    [
        'name'          => 'Keyboard',
        'description'   => 'Mechanical Keyboard',
        'value'         =>  250.00,
        'date_register' => '2017-06-23',
    ],
];

// Render our view
echo $twig->render('index.html', ['products' => $products] );

This is a simple example; we are creating an array containing products, like our mechanical keyboard, that we can use in our template. Then, we use the render() method which accepts a template name (this is a file inside the template folder that we defined earlier) and the data that we want to pass to the template.

To complete our example, let’s go inside our /templates folder and create an index.html file. First, let’s take a look at the template itself.

<!DOCTYPE html>
<html lang="pt-BR">
    <head>
        <meta charset="UTF-8">
        <title>Twig Example</title>
    </head>
    <body>
    <table border="1" style="width: 80%;">
        <thead>
            <tr>
                <td>Product</td>
                <td>Description</td>
                <td>Value</td>
                <td>Date</td>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
                <tr>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.value }}</td>
                    <td>{{ product.date_register|date("m/d/Y") }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
    </body>
</html>

Opening index.php in the browser (by visiting localhost or homestead.app, depending on how you’ve set up your hosts and server) should produce the following screen now:

Rendered table

But let’s go back and take a closer look at our template code.

There are two types of delimiters: {{ ... }} is used to print the result of an expression or an operation and {% ... %} is used to execute statements like conditionals and loops. These delimiters are Twig’s main language constructs, and are what Twig uses to “inform” the template that it has to render a Twig element.

Layouts

In order to avoid the repetition of elements (like headers and footers) in our templates, Twig offers us the ability to nest templates inside of templates. These are called blocks.

To exemplify this, let’s separate actual content from the HTML definition in our example. Let’s create a new HTML file and call it layout.html:

<!DOCTYPE html>
<html lang="pt-BR">
    <head>
        <meta charset="UTF-8">
        <title>Tutorial Example</title>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

We created a block called content. What we are saying is that every template that extends from layout.html may implement a content block that will be displayed in that position. This way we can reuse the layout multiple times without having to rewrite it. In our case, the index.html file will now look like this:

{% extends "layout.html" %}

{% block content %}
    <table border="1" style="width: 80%;">
        <thead>
            <tr>
                <td>Product</td>
                <td>Description</td>
                <td>Value</td>
                <td>Date</td>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
                <tr>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.value }}</td>
                    <td>{{ product.date_register|date("m/d/Y") }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

Twig also allows us to render just a single block. To do that we need to first load a template and then render the block.

$template = $twig->load('index.html');
echo $template->renderBlock('content', array('products' => $products));

At this point, we still have the same page, but we reduced the complexity of it by separating contextual blocks.

Cache

The Environment object can be used for more than just loading templates.

If we pass the cache option with a directory associated, Twig will cache the compiled templates so it avoids the template parsing in subsequent requests. The compiled templates will be stored in the directory we provided. Be aware that this is a cache for compiled templates, not evaluated ones. What this mean is that Twig will parse, compile and save the template file. All subsequent requests will still need to evaluate the template, but the first step is already done for you.

Let’s cache the templates in our example by editing our bootstrap.php file:

$twig = new Twig_Environment($loader, ['cache' => '/templates/cache']);

Loops

In our example we’ve already seen how a loop is done using Twig. Basically, we use the for tag and assign an alias for each element in the specified array. In our case, we assigned the alias product for our products array. After that, we can access all the attributes in each array element by using the . operator. We use the endfor tag to indicate the end of our loop.

We can also loop through numbers or letters using the .. operator. Just like the following:

{% for number in 0..100 %}
     {{ number }}
{% endfor %}

or for letters:

{% for letter in 'a'..'z' %}
     {{ letter }}
{% endfor %}

This operator is just syntactic sugar for the range function which works just like the native PHP range function.

Also useful is the option to add a condition to a loop. With a condition, we’re able to filter which elements we want to iterate through. Imagine we want to iterate through all products whose value is less than 250:

<tbody>
    {% for product in products if product.value < 250 %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ product.description }}</td>
            <td>{{ product.value }}</td>
            <td>{{ product.date_register|date("m/d/Y") }}</td>
    </tr>
    {% endfor %}
</tbody>

Conditionals

Twig also offers us conditionals in the form of the tags if, elseif, if not, and else. Just like in any programming language, we can use these tags to filter for conditions in our template.

Imagine that, in our example, we only want to show products with a value above 500.

<tbody>
    {% for product in products %}
    {% if product.value > 500 %}
                <tr>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.value }}</td>
                    <td>{{ product.date_register|date("m/d/Y") }}</td>
        </tr>
    {% endif %}
    {% endfor %}
</tbody>

Filters

Filters allow us to filter what information is passed to our template and in which format it is shown. Let’s look at some of the most used and important ones. The full list of Twig filters can be found here.

Date and date_modify

The date filter formats a date to a given format. As we can see in our example:

<td>{{ product.date_register|date("m/d/Y") }}</td>

We are showing our date in a month/day/year format. On top of the date filter, we can change the date with a modifier string using the date_modify filter. For example, if we wanted to add a day to our date we could use the following:

<td>{{ product.date_register|date_modify("+1 day")|date("m/d/Y") }}</td>

Format

Formats a given string by replacing all the placeholders. For example:

<td>{{ "This product description is: %s"|format(product.description) }}</td>

Striptags

The striptags filter strips SGML/XML tags and replaces adjacent white space with one space:

{{ <p>Hello World</p>|striptags }}`

Escape

Escape is one of the most important filters. It filters a string for safe insertion in the final output. By default, it uses the HTML escaping strategy, so

{{ products.description|escape }}

is equivalent to

{{ products.description|escape('html') }}

The js, CSS, URL and html_attr escaping strategies are also available. They escape the string for the Javascript, CSS, URI and HTML attribute contexts respectively.

Debug

Lastly, let’s take a look at debugging. Sometimes we need to access all the information on a template variable. For that effect Twig has the dump() function. This function is not available by default. We must add the Twig_Extension_Debug extension when creating our Twig environment:

$twig = new Twig_Environment($loader, array('debug' => true));
$twig->addExtension(new Twig_Extension_Debug());

This step is needed so we don’t accidentally leak debug information on a production server. After configuring it, we can just use the dump() function to dump all the information about a template variable.

{{ dump(products) }}

Conclusion

Hopefully, this article can give you a solid base on the fundamentals of Twig, and get your projects started right away! If you want to go deeper into Twig, the official website offers a very good documentation and reference that you can consult.

Do you use a template engine? What do you think of Twig? Would you compare it with popular alternatives like Blade, or even Smarty?