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

May 2, 2011

Zend Framework

Feature Power

In the first segment of this article, I introduced you to Twig, a PHP-based template engine created and maintained by Sensio Labs and Fabien Potencier. I walked you through the process of installing Twig, understanding how templates and template variables work, and using Twig’s built-in conditional and loop constructs to automate some aspects of template development. I also gave you a crash course in some of Twig’s built-in filters, which provide a convenient way to quickly manipulate template content.

In this second and concluding segment, I’ll look at some of Twig’s other features, including such goodies as template inheritance, custom filters and caching. If you enjoyed the first part of this article, keep reading to find out more about what goes on under Twig’s hood, and how you can add even more power and flexibility to your templates.

Nest Egg

One of Twig’s most powerful features is its support for template inheritance. This means that you can define a base template and derive child templates from it, overriding key elements of the parent on an as-needed basis within the children. This offers a great deal of flexibility when defining the interface for a Web application, as you can use this feature to create a nested template structure for a Web application and override sections of each page as needed.

The keys to Twig’s template inheritance system are the 'extends' tag, which allows one template to extend another, and the 'block' tag, which allows a designer to decompose a page into separate, independent units. To better understand how this works, assume that you have a base template containing a block for an unordered list:

<html>
  <head></head>
  <body>   
    <h3>Unordered list</h3>
    <ul>
    {% block list %}
      <li>one</li>
      <li>two</li>
      <li>three</li>
    {% endblock list %}
    </ul>
  </body>
</html>

This template uses the 'block' tag to define a block within the template. This block can now be overridden as needed from within a child template. To illustrate, consider one such child template:

{% extends "parent.tmpl" %}

    {% block list %}
      <li>red</li>
      <li>yellow</li>
      <li>orange</li>
    {% endblock list %}

Notice the 'extends' tag in the child template; this indicates that the template extends another one. When extending a parent template in this manner, blocks in the child template will override identically-named blocks in the parent. Consider the following script, which renders the child template and illustrates the result:

<?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('child.tmpl');

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

Here’s the output:

As you can see from the output, the list in the child template has overridden the list in the parent template.

Block Print

Another useful feature of Twig, with respect to inheritance, relates to usage of parent blocks within child templates. Twig provides a parent() function, which can be used within a child template’s block to render the contents of the parent template. Consider the following template, which carries on from the previous example and illustrates the process:

{% extends "child.tmpl" %}

{% block main %}
{{ parent() }}
{% endblock main %}

Here’s what the output looks like:

Twig doesn’t let you have two blocks with the same name within the same template, which can pose a problem if you want to repeat blocks. Fortunately, there’s a block() function, which can be used to import the result of a block into a specific section of a template. Here’s an example of it in use:

<html>
  <head></head>
  <body>   
    <h3>Unordered list</h3>
    <ul>
    {% block list %}
      <li>one</li>
      <li>two</li>
      <li>three</li>
    {% endblock list %}
    </ul>
    <h3>Ordered list</h3>
    <ol>
    {{ block('list') }}
    </ol>
  </body>
</html>

In this case, the block() function will display the list from the first block again, as shown below:

Fun With Filters

In the previous article, I showed you how to use Twig’s built-in filters to perform direct manipulation of template content, such as upper-casing text blocks or formatting date and time values. However, you’re not restricted only to using Twig’s built-in filters; you can also define your own.

New filters are defined using the Twig_Filter_Function object, and passed to the Twig_Environment using the addFilter() function. This function accepts two arguments: the name of the filter and the name of the PHP function to use when the filter is invoked. Here’s an example, which illustrates by building a Twig filter to obfuscate email addresses:

<?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);
  
  // add custom filter
  $twig->addFilter('obfuscate_address', new Twig_Filter_Function('obfuscate_address'));
  
  // load template
  $template = $twig->loadTemplate('example.tmpl');

  // set template variables
  // render template
  echo $template->render(array('email' => 'vikram@example.com'));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}

function obfuscate_address($str) {
    return str_replace(
      array('@', '.', '-'), 
      array(' at ', ' dot ', ' dash '), 
      $str
    );
}
?>

Here’s how you’d use this in a Twig template:

<html>
  <head></head>
  <body>   
    Write to me at: {{ email|obfuscate_address }}
  </body>
</html>

And here’s an example of what the output looks like:

A Good Argument

It’s also possible to define custom filters that accept arguments, as shown in this revision of the previous example:

<?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);
  
  // add custom filter
  $twig->addFilter('obfuscate_address', 
    new Twig_Filter_Function('obfuscate_address', array('is_safe' => array('html')))
  );
  
  // load template
  $template = $twig->loadTemplate('example.tmpl');

  // set template variables
  // render template
  echo $template->render(array('email' => 'vikram@example.com'));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}

function obfuscate_address($str, $display_as_link = 0) {
    $newStr = str_replace(
      array('@', '.', '-'), 
      array(' at ', ' dot ', ' dash '), 
      $str
    );
    return ($display_as_link == 0) ? $newStr : '<a href="mailto:' . $str . '">' . $newStr . '</a>';
}
?>

In this case, the 'obfuscate_address' filter will accept an additional argument, indicating whether the email address should be displayed as a hyperlink or not. Here’s an example of a template that uses this revised filter:

<html>
  <head></head>
  <body>   
    Write to me at: {{ email|obfuscate_address(1) }}
  </body>
</html>

And here’s what the output looks like:

Notice also the 'is_safe' array being passed in the previous listing. This specifies that it is safe to display the output as unescaped HTML.

In a similar vein, it’s possible to define Twig functions using the Twig_Function_Function object and the addFunction() method. Here’s a simple example, which defines a truncate() function:

<?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);
  
  // add custom function
  $twig->addFunction('truncate', new Twig_Function_Function('truncate'));
  
  // load template
  $template = $twig->loadTemplate('example.tmpl');

  // set template variables
  // render template
  echo $template->render(array('title' => 'This is a very long title'));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}

function truncate($str, $length) {
    return (strlen($str) > $length) ? trim(substr($str, 0, $length)) . '...' : $str;
}
?>

And here’s an example of a template that uses this function:

<html>
  <head></head>
  <body>   
    {{ truncate(title, 12) }}
  </body>
</html>

Here’s what the output looks like:

The Need For Speed

Twig will automatically compile its templates before rendering them. On high-traffic Web sites, it’s possible to cache the compiled templates and reuse them as needed, thereby improving performance by orders of magnitude. This “compilation cache” can be controlled by two configuration variables, which must be passed to the Twig_Environment object:

  • The 'cache' option specifies the location of the cache directory. If this directory does not already exist, Twig will create it automatically.
  • The 'auto_reload' option specifies whether the template should be recompiled when Twig detects a change in the template file. It’s generally a good idea to keep this on during development, or else you might wonder why your rendered output doesn’t reflect recent changes in the template code; you can however turn it off in a production environment where templates are unlikely to change frequently.

Here’s an example of how these two variables can be used in a PHP script:

<?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, array(
    'cache'       => 'cache/templates',
    'auto_reload' => true
  ));
  
  // load template
  $template = $twig->loadTemplate('example.tmpl');

  // set template variables
  // render template
  echo $template->render(array(
    'hello'  => 'Hello world',
    'now'    => date('d M Y h:i:s', mktime())
  ));
  
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

Here’s the corresponding template:

<html>
  <head></head>
  <body>   
    {{ hello }}. It is now {{ now }}
  </body>
</html>

And now, when you run the previous script, you’ll see that Twig creates a cache directory and stores the compiled template output in that directory. Subsequent requests for the same script will use the cached, compiled version instead of recompiling it each time. When the template code changes, Twig will automatically detect the change and recompile and recache the template.

Cache Cow

It’s important to point out that the compilation cache discussed on the previous page only caches the compiled template code, and not the rendered output of the page. You can, however, integrate Twig with any external caching system, such as Zend_Cache or PEAR Cache, to also cache the rendered output of each page.

When output caching is enabled, pages can be directly rendered from the cache, without needing to re-execute the PHP code inside the template on each run. For pages that contain many lines of PHP code – for example, pages that use database result sets or integrate data from third-party Web services – this feature can result in massive performance improvements.

To see how this works, consider the following revision of the previous script, which combines Twig’s compilation cache with Zend_Cache’s output cache to further improve page performance:

<?php
include 'Zend/Cache.php';

// define cache options
$frontendOptions = array(
   'lifetime' => 300,
   'automatic_serialization' => true
);

$backendOptions = array(
    'cache_dir' => 'cache/output'
);

try {
  // configure cache
  $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
  $id = 'z100';

  // if data not present in cache
  // generate and save it to cache for next run
  if(!($data = $cache->load($id))) {
    // include and register Twig auto-loader
    include 'Twig/Autoloader.php';
    Twig_Autoloader::register();

    // define template directory location
    $loader = new Twig_Loader_Filesystem('templates');
      
    // initialize Twig environment
    $twig = new Twig_Environment($loader, array(
      'cache'       => 'cache/templates',
      'auto_reload' => true
    ));
      
    // load template
    $template = $twig->loadTemplate('example.tmpl');

    // set template variables
    // render template
    $c = $template->render(array(
      'hello'  => 'Hello world',
      'now'    => date('d M Y h:i:s', mktime())
    ));
      
    $cache->save($c, $id);
    $data = $c;
  }
  echo $data;
    
} catch (Exception $e) {
  die ('ERROR: ' . $e->getMessage());
}
?>

There’s a three-step process to enable output caching with Zend_Cache:

  1. Specify a unique cache identifier and cache duration for the page as arguments to the Zend_Cache frontend, and a directory location for the cache as an argument to the Zend_Cache backend. Note that unlike with the Twig compilation cache, this cache directory must already exist, or else Zend_Cache will throw an error.
  2. Wrap your template generation code in calls to the Zend_Cache load() method, and return the page from the cache if a cached copy exists.
  3. If a cached copy does not exist or is stale, generate the page afresh and save it to the cache so it can be reused for subsequent requests.

In the previous script, these steps have been combined with the Twig compilation cache, such that stale data in the output cache is refreshed using templates from the compilation cache.

Plugging In

If you like to develop your applications using a framework (and why wouldn’t you?), then you’ll be interested to learn that you can also use Twig with the Zend Framework. A number of different techniques are available, but the simplest is the one proposed by Benjamin Zikarsky in this discussion thread, and it also forms the basis for the material on this page.

To understand how it works, assume for a moment that you’re creating a new Zend Framework project, and you wish to replace the Zend_View component of the Zend Framework with Twig. First, ensure that the Twig library files are accessible from within the Zend Framework project, by copying them to the library/ directory or adding them to the PHP include path.

Next, create a Twig adapter for the Zend Framework that implements Zend_View_Interface, by copying Benjamin Zikarsky’s original code and saving it to your project directory tree under library/Twig/Adapter/ZendView.php. While you’re there, find and replace the adapter’s getScriptPaths() method with this revised version:

<?php
...
    public function getScriptPaths()
    {
        $loader = $this->getLoader();
        $path = ($loader instanceof Twig_Loader_FileSystem) 
            ? $loader->getPaths()   
            : '';

        return $path;
    }
...

Next, update the application bootstrapper with a new _initTwig() method, which takes care of initializing the Twig adapter and defining the base path for the application templates. Note that Twig will automatically add the controller and template name to this path, assuming that they follow standard Zend Framework conventions.

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initTwig()
    {
        // initialize view
        // set path to view scripts
        $view = new Twig_Adapter_ZendView(APPLICATION_PATH."/views/scripts");

        // initialize view renderer and connect it to view
        $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
        $viewRenderer->setView($view);
    }                 
}

You should now be able to use Twig syntax in your controllers and view scripts, as shown below:

In application/controllers/IndexController.php:

<?php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $this->view->message = "Welcome to the Zend Framework with Twig";
    }
}

In application/views/scripts/index/index.phtml:

<h1> {{ message }} </h1>

Here’s what the output looks like:

For more information on (and limitations of) this approach, in addition to some options, take a look at the original discussion thread in the Twig users mailing list.

And that’s about all we have time for. As these examples illustrate, Twig comes with a number of useful features for developers looking to expand their repertoire and create complex Web sites and applications using templates. Its support for template inheritance speeds up development time and reduces the effort involved in maintaining a Web application, while the filter system allow for easy integration of custom functionality. Built-in compilation caching can help improve performance on high-traffic pages, and easy usability in both standalone and framework mode makes it an attractive and convenient option for PHP developers of all stripes. Take a look at it sometime and see for yourself!

Copyright Melonfire, 2011. All rights reserved.

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

  1. regjack Says:

    Very interesting article on how to use twig in zend framework. Thanks for sharing!
    http://regjackonline.com

  2. miros74 Says:

    Nice Article about using twig in zend framework. Thanks
    http://miroslavmorant.com

  3. bieleke Says:

    Hi,

    Nice Article about using twig in zend framework, but you are not mentioning what to do with the layout part of zend framework.

    Is the layout still loaded as default, or should you disable it and use the extends feature of twig to call the right master file that takes care of the layout ? Because face it, that is the real advantage of using twig. For example you could easily, by just extending the wanted layout file, choose various layout files without using

    $this->_helper->layout->setLayout(‘other-layout’);

    in your controller or it’s init function.

  4. suzilnicholas Says:

    hi, i am new in this site and i read about the php coding so i want to ask you that how we can use easy step to create a good programe…

    http://www.cellhub.com/t-mobile-cell-phone-plans/even-more-plans.html

    T-Mobile Plans

  5. samsonasik Says:

    if i want to add layout, how must i do ?