Creating Web Page Templates with PHP and Twig (part 1)

April 6, 2011

Tutorials

Enforced Separation

There’s no shortage of PHP template engines and they’re all very similar, often differing only in implementation details and support for specific features. They all serve the same purpose: that of functionally separating a Web application’s user interface from its business logic, and thereby enabling developers and interface designers to work on the same application without getting in each other’s hair.

Most PHP frameworks, including Zend Framework, Agavi, CakePHP and CodeIgniter, come with built-in templating to enable this separation. However, if you’re not a big fan of frameworks, or if your project is small enough that you don’t need the additional overhead, you can also consider using a standalone template engine to obtain the same benefits. A number of such engines exist – you’ve probably already heard of Smarty, Savant, Dwoo and others – and in this article, I’ll introduce you to one I discovered rather recently, called Twig.

Back to Basics

This tutorial makes a couple of assumptions. First, it assumes that you know the basics of PHP and database programming, and you have a reasonably-good understanding of using objects and object methods in PHP. Second, it assumes that you have an Apache/PHP/MySQL development environment already set up, and that you’ve managed to successfully install Twig. If you’re using the automated PEAR installer, you can install Twig simply by running the following commands at your command prompt:

shell> pear channel-discover pear.twig-project.org
shell> pear install twig/Twig

Alternatively, you can manually install Twig, by downloading it from here. Twig is currently maintained by Sensio Labs and Fabien Potencier, who is also the creator of the Symfony framework, and is released under the new BSD License. This article uses Twig v1.0.

Before you can start using Twig, a quick overview of the basics. Consider that the typical PHP application consists of a number of Web pages, each containing a combination of static HTML elements (navigation menus, lists, images and so on) and dynamic content (data from a database, an XML file, a Web service and so on). Twig provides a way to separate the two, by creating templates containing placeholders for the dynamic content and proving an API to inject “real” content into these placeholders at run-time.

The value for the placeholders are defined inside a controlling PHP script; this script also takes care of the intricacies of database connections and queries, XML processing and additional calculations. Through this process, the Web page is generated by interpolating content from two sources: the template, which contains the static user interface elements and placeholders, and the controlling PHP script, which retrieves data for the placeholders from the appropriate data sources. Programmers and user interface designers are now able to work on the same Web page simultaneously, without needing significant additional coordination (apart from agreeing on the names of the common placeholder variables).

Locked and Loaded

To see how this works, here’s a simple illustrative template:

<html>
  <head></head>
  <body>
  <h2>Account successfully created!</h2>
  
  <p>Hello {{ name }}</p>
  
  <p>Thank you for registering with us. Your account details are as follows: </p>
  
  <p style="margin-left: 10px">
  Username: {{ username }} <br/>
  Password: {{ password }}
  </p>
  
  <p>You've already been logged in, so go on in and have some fun!</p>
  </body>
</html>

Save this file as templates/thanks.tpl. Notice that the placeholder variables are surrounded by two sets of curly braces; this tells Twig that they are template variables and must be replaced with actual values at run-time.

Next, create a controller script that populates these variables with actual data and renders the composite output:

<?php
// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

try {
  // specify where to look for templates
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('thanks.tmpl');
  
  // set template variables
  // render template
  echo $template->render(array(
    'name' => 'Clark Kent',
    'username' => 'ckent',
    'password' => 'krypt0n1te',
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

And now, when you access this PHP script through a browser, you should see something like this:

There’s a fairly standard sequence to rendering a template using Twig:

  1. Initialize the Twig autoloader. This registers the Twig autoloader with PHP, so that Twig classes and plugin files are automatically found and loaded as needed.
  2. Initialize a template loader. This is done by creating an instance of a class which implements the Twig_LoaderInterface – in this case, the Twig_Loader_Filesystem. The object constructor is passed the name of the directory in which to look for templates.
  3. Initialize the Twig environment. This is done by creating an instance of Twig_Environment, which stores the current Twig configuration.
  4. Load a template. The environment object exposes a loadTemplate() method, which can be passed a template file name and which finds and loads the relevant template into memory. The return value of this method is a template instance.
  5. Set values for template variables and render the template. A new associative array, whose values correspond to template variable names, should be defined and then passed to the template object’s render() method. The render() method interpolates the values from the data array into the template object, generating and rendering the composite result.

Getting Even

Twig also lets you conditionally display content within a template, through its ‘if-else-endif’ control structure. Here’s a simple example of a template that uses this structure:

<html>
  <head></head>
  <body>
    <h2>Odd or Even</h2>
    {% if div == 0 %}
      {{ num }} is even.
    {% else %}
      {{ num }} is odd.
    {% endif %}
  </body>
</html>

Depending on the number generated within the PHP controller script, the template will display one of two possible messages. Here’s the controller script, which randomly picks a number between 0 and 30, and then divides it by 2 to check whether it’s odd or even:

<?php
// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

try {
  // define template directory location
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('numbers.tmpl');

  // generate random number and
  // check whether odd or even
  $num = rand (0,30);
  $div = ($num % 2);  

  // set template variables
  // render template
  echo $template->render(array (
    'num' => $num,
    'div' => $div
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

Here’s a sample of the output:

You can also handle multiple conditions within a Twig template, through the ‘if-elseif-else-endif’ control structure. Here’s an example of a template that uses this:

<html>
  <head></head>
  <body>
    <h2>Seasons</h2>
    {% if month > 0 and month <= 3 %}
      Spring is here, watch the flowers bloom!
    {% elseif month > 3 and month <= 6 %}
      Summer is here, time to hit the beach!
    {% elseif month > 6 and month <= 9 %}
      Autumn is here, watch the leaves slowly fall!
    {% elseif month > 9 and month <= 12 %}
      Winter is here, time to hit the slopes!
    {% endif %}
  </body>
</html>

And here’s the PHP controller script, which retrieves the current month number and passes it along to the template:

<?php
// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

try {
  // define template directory location
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('seasons.tmpl');

  // get current month number
  $month = date('m', mktime());

  // set template variables
  // render template
  echo $template->render(array (
    'month' => $month
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

Here’s an example of the output:

Looping The Loop

Twig also supports ‘for’ loops, allowing you to repeat a particular block of a template over and over again. This is quite handy, especially when looping over arrays. To illustrate, consider the following template:

<html>
  <head></head>
  <body>
    <h2>Shopping list</h2>
    <ul>
      {% for item in items %}
        <li>{{ item }}</li>
      {% endfor %}
    </ul>    
  </body>
</html>

In this template, items is an indexed array, which is populated by the PHP controller script. On each iteration, the current element of the array is assigned to the local loop variable item and printed as a list item. Here’s the PHP controller script, which sets up the items array:

<?php
// define array of values
$items = array(
  'eye of newt',
  'wing of bat',
  'leg of frog',
  'hair of beast'
);

// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

try {
  // define template directory location
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('list.tmpl');

  // set template variables
  // render template
  echo $template->render(array (
    'items' => $items
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

And here’s what the output looks like:

Twig also supports so-called “dot notation” for accessing the keys of an associative array or the properties of an object. To illustrate, consider the following PHp controller script, which defines an associative array of values and passes them to a Twig template:

<?php
// define array of values
$book = array(
  'title'     => 'Harry Potter and the Deathly Hallows',
  'author'    => 'J. K. Rowling',
  'publisher' => 'Scholastic',
  'category'  => 'Children\'s fiction',
  'pages'     => '784'
);

// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

try {
  // define template directory location
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('book.tmpl');

  // set template variables
  // render template
  echo $template->render(array (
    'book' => $book
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

Within the template, the keys of the associative array can be accessed by suffixing the template variable name with a period and the key name, as shown below:

<html>
  <head>
    <style type="text/css">
      table {
        border-collapse: collapse;
      }        
      tr.heading {      
        font-weight: bolder;
      }        
      td {
        border: 1px solid black;
        padding: 0 0.5em;
      }    
    </style>    
  </head>
  <body>
    <h2>Book details</h2>
    <table>
      <tr>
        <td><strong>Title</strong></td>
        <td>{{ book.title }}</td>
      </tr>
      <tr>
        <td><strong>Author</strong></td>
        <td>{{ book.author }}</td>
      </tr>
      <tr>
        <td><strong>Publisher</strong></td>
        <td>{{ book.publisher }}</td>
      </tr>
      <tr>
        <td><strong>Pages</strong></td>
        <td>{{ book.pages }}</td>
      </tr>
      <tr>
        <td><strong>Category</strong></td>
        <td>{{ book.category }}</td>
      </tr>
    </table>
  </body>
</html>

Here’s what the output looks like:

This also works with objects…as you’ll see on the next page!

Data Dump

The ability to use loops inside template comes in particularly handy when you need to integrate the results of a database query into your output template. To illustrate, consider the following template for a five-column table:

<html>
  <head>
    <style type="text/css">
      table {
        border-collapse: collapse;
      }        
      tr.heading {      
        font-weight: bolder;
      }        
      td {
        border: 1px solid black;
        padding: 0 0.5em;
      }    
    </style>  
  </head>
  <body>
    <h2>Countries and capitals</h2>
    <table>
      <tr class="heading">
        <td>Country</td>
        <td>Region</td>
        <td>Population</td>
        <td>Capital</td>
        <td>Language</td>
      </tr> 
      {% for d in data %}
      <tr>
        <td>{{ d.name|escape }}</td>
        <td>{{ d.region|escape }}</td>
        <td>{{ d.population|escape }}</td>
        <td>{{ d.capital|escape }}</td>
        <td>{{ d.language|escape }}</td>
      </tr> 
      {% endfor %}
    </table>
  </body>
</html>

This template is populated with the following PHP script, which uses PDO to connect to the MySQL ‘world’ database, retrieve a list of countries and cities, and package this data into a template variable:

<?php
// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

// attempt a connection
try {
  $dbh = new PDO('mysql:dbname=world;host=localhost', 'root', 'guessme');
} catch (PDOException $e) {
  echo "Error: Could not connect. " . $e->getMessage();
}

// set error mode
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// attempt some queries
try {
  // execute SELECT query
  // store each row as an object
  $sql = "SELECT country.Code AS code, country.Name AS name, country.Region AS region, country.Population AS population, countrylanguage.Language AS language, city.Name AS capital FROM country, city, countrylanguage WHERE country.Code = city.CountryCode AND country.Capital = city.ID AND country.Code = countrylanguage.CountryCode AND countrylanguage.IsOfficial = 'T' ORDER BY population DESC LIMIT 0,20";
  $sth = $dbh->query($sql);
  while ($row = $sth->fetchObject()) {
    $data[] = $row;
  }
  
  // close connection, clean up
  unset($dbh); 

  // define template directory location
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('countries.tmpl');

  // set template variables
  // render template
  echo $template->render(array (
    'data' => $data
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

Notice two things about the above listing:

PDO’s fetchObject() method is used to return each row of the database result set as an object, whose properties correspond to column names. These row objects are then placed in an array, and passed to the Twig template. Within the template, dot notation is used to access the individual columns of each row and print the corresponding values.

This template make use of a built-in Twig filter, ‘escape’, which takes care of automatically escaping output before printing it. You should filter all output in this manner to reduce your vulnerability to XSS attacks. By default, the ‘escape’ filter escapes output using PHP’s htmlspecialchars() function.

Here’s what the output looks like:

Some Assembly Required

Twig also comes with an ‘include’ statement, which can be used to load templates from other files into a container template. This is handy when you have common page elements, like menus or header/footer bars, and you want to use them multiple times, but maintain them in a single location.

To illustrate how this works, consider the following top-level template, which internally makes use of three separate sub-templates:

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="main.css" />
  </head>
  <body>
    <div id="page">
      <div id="header">
      {% include 'primary.tmpl' %}
      </div>

      <div id="left">
        {% include 'secondary.tmpl' %}
      </div>
    
      <div id="right">
      This is the main page content. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    
      <div id="footer">
        {% include 'footer.tmpl' %}
      </div>
    </div>  
  </body>
</html>

Three sections of this page – the primary header menu, the secondary left menu, and the page footer – are loaded from separate template files via Twig’s ‘include’ statement. Here’s what each of these looks like:

    <!-- begin: primary.tmpl -->
    <table>
      <tr>
        {% for item in nav.primary %}
        <td><a href="{{ item.url }}">{{ item.name|upper }}</a></td>
        {% endfor %}
      </tr> 
    </table>
    <!-- end: primary.tmpl -->
    <!-- begin: secondary.tmpl -->
    <ul>
      {% for item in nav.secondary %}
      <li><a href="{{ item.url }}">{{ item.name }}</a></li>
      {% endfor %}
    </ul> 
    <!-- end: secondary.tmpl -->
    <!-- begin: footer.tmpl -->
    <div style="align:center">
    This page licensed under a Creative Commons License. Last updated on: {{ updated }}. 
    </div> 
    <!-- end: secondary.tmpl -->

And here’s the PHP controller script that brings it all together:

<?php
// define navigation array
$nav = array(
  'primary' => array(
    array('name' => 'Clothes', 'url' => '/clothes'),
    array('name' => 'Shoes and Accessories', 'url' => '/accessories'),
    array('name' => 'Toys and Gadgets', 'url' => '/toys'),
    array('name' => 'Books and Movies', 'url' => '/media'),
  ),
  'secondary' => array(
    array('name' => 'By Price', 'url' => '/selector/v328ebs'),
    array('name' => 'By Brand', 'url' => '/selector/gf843k2b'),
    array('name' => 'By Interest', 'url' => '/selector/t31h393'),
    array('name' => 'By Recommendation', 'url' => '/selector/gf942hb')
  )
);

// include and register Twig auto-loader
include 'Twig/Autoloader.php';
Twig_Autoloader::register();

try {
  // define template directory location
  $loader = new Twig_Loader_Filesystem('templates');
  
  // initialize Twig environment
  $twig = new Twig_Environment($loader);
  
  // load template
  $template = $twig->loadTemplate('shop.tmpl');

  // set template variables
  // render template
  echo $template->render(array (
    'nav' => $nav,
    'updated' => '24 Jan 2011'
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

Notice that it isn’t necessary to call loadTemplate() for each of the individual sub-templates; all that’s needed is to call the top-level template and that template will, in turn, load each individual sub-template as needed. Variables defined in the PHP controller script can be accessed by the individual sub-templates as well.

Here’s an example of what the output looks like:

Filter Fever

Twig comes with a bunch of built-in filters that you can use to perform quick-and-dirty manipulation of template content. Needless to say, you can also define your own filters (a topic that’s covered later in this tutorial).

First up, the ‘date’ filter. This filter makes it possible to format dates and times using standard PHP date format specifiers. Here’s an example:

<html>
  <head></head>
  <body>
  {{ "now"|date("d M Y h:i")  }} <br/>
  {{ "now"|date("d/m/y")  }}
  </body>
</html>

Here’s a sample of the output:

You can also alter the case of template variables using the ‘upper’, ‘lower’, ‘capitalize’ and ‘title’ filters. Here’s an example that demonstrates:

<html>
  <head></head>
  <body>
  {{ "the cow jumped over the moon"|upper  }} <br/>
  {{ "the cow jumped over the moon"|capitalize  }} <br/>
  {{ "the cow jumped over the moon"|title  }} <br/>
  {{ "The Cow jumped over the Moon"|lower  }} <br/>
  </body>
</html>

Here’s what the output looks like:

The ‘striptags’ filter removes all the HTML and XML tags from a template variable, as shown below:

<html>
  <head></head>
  <body>
  {{ "<div>I said \"<b>Go away!</b>\"</div>"|striptags  }} <br/>
  </body>
</html>

Here’s the stripped output:

The ‘replace’ filter provides an easy to perform in-place string replacement. It accepts a hash of key-value pairs, and replaces all the keys in the string with the corresponding values. Here’s an example:

<html>
  <head></head>
  <body>
  {{ "I want a red boat"|replace({"red" : "yellow", "boat" : "sports car"})  }} <br/>
  </body>
</html>

Here’s what the output looks like:

You’ve already seen the ‘escape’ filter in use earlier, but there’s also a ‘raw’ filter which does the opposite: allows HTML code to pass through unsanitized. Remember that this should only be used on input that you’re completely sure is safe. Here’s an example:

<html>
  <head></head>
  <body>
  Escaped output: {{ html|escape }} <br/>
  
  Raw output: {{ html|raw }} <br/>
  </body>
</html>

Here’s what the output looks like:

If you have a large block of text to be escaped or unescaped, you can also enclose it within an ‘autoescape’ block, passing a Boolean true/false argument to turn escaping on or off. Here’s an example, which is equivalent to the previous one:

<html>
  <head></head>
  <body>
  {% autoescape true %}
  Escaped output: {{ html }} <br/>
  {% endautoescape %}  
  
  {% autoescape false %}
  Raw output: {{ html }} 
  {% endautoescape %}  
  </body>
</html>

At this point, you should have a good idea of the basics of using Twig, together with a broad understanding of available control structures and filters. In the second and final part of this article, I’ll go a little deeper, looking into Twig’s more advanced features, such as template inheritance, custom extensions, configuration and much more. Make sure you come back for that…and happy coding!

Copyright Melonfire, 2011. All rights reserved.

2 Responses to “Creating Web Page Templates with PHP and Twig (part 1)”

  1. federico81 Says:

    Nice article I especially enjoy the clean HTML you made, the Django style looks elegant, but I personally prefer the Smarty style because more similar to PHP syntax.

    Anyway here you can also see the performances of Twig compared with other template engines
    http://phpcomparison.net

    Hope to see part 2 as well

  2. iluvphp Says:

    Hi,

    I really enjoyed this article and have been considering using a template engine and been wanting to try one for a long time but so many are bogged down and requires us to learn practically a new language.

    I would really love to see part 2 come asap as i want to get started on using twig and this has been the best tutorial i have yet to find. Part 1 was excellent. I would love to see how you would use it in a more real php application to give a little better understanding to use newbies out there that are new to template engines etc.

    Thanks and hopefully part 2 will soon arrive