a Sensio Labs Product

The flexible, fast, and secure
template language for PHP

Twig 0.9.8 released

Twig 0.9.8 has just been released. This is a maintenance release that fixes several annoying bugs.

This release introduces a backward incompatibility for the trans tag:

[twig]
{# old syntax #}
{% trans count %}...{% plural %}...{% endtrans %}

{# new syntax #}
{% trans %}...{% plural count %}...{% endtrans %}

And here is the full list of fixed bugs:

  • added a way to translate strings coming from a variable ({% trans var %})
  • fixed trans tag when used with the Escaper extension
  • fixed default cache umask
  • removed Twig_Template instances from the debug tag output
  • fixed objects with __isset() defined
  • fixed set tag when used with a capture
  • fixed type hinting for Twig_Environment::addFilter() method

Twig 0.9.7 released

Just a month after the release of Twig 0.9.6, I’m pleased to announce the availability of Twig 0.9.7.

As always, the documentation has been updated and the changelog lists all the changes we made. This post only mentions the main changes.

Backwards Incompatibilities

set tag

The set tag syntax changed for better readability:

{# before 0.9.7 #}

{% set title as "Title" %}

{# as of 0.9.7 #}

{% set title = "Title" %}

include tag

The sandboxed attribute of the include tag has been removed in favor of the new sandbox tag:

{# before 0.9.7 #}

{% include "foo.twig" sandboxed %}

{# as of 0.9.7 #}

{% sandbox %}
    {% include "foo.twig" %}
{% endsandbox %}

The include tag also accepts Twig_Template instances as argument.

Nodes

The Node sub-system has been refactored. If you have custom Nodes, you must update them and use the new API:

  • The Twig_NodeList class has been removed. If you tag extends it, you can safely replace it with Twig_Node and remove the getNodes() and setNodes() methods.

  • The __toString() method is now generated automatically by the Twig_Node base class. Most of the time, you can just remove it if you have defined it.

  • The main change is the constructor (read the Twig_Node PHPDoc for more information).

New Features

Dynamic and Conditional Inheritance

Such a big enhancement deserves it’s own blog post.

sandbox tag

As already mentioned, the new sandbox tag allows you to sandbox snippets of code:

{% sandbox %}
    {% include "foo.twig" %}
    {% include "bar.twig" %}
{% endsandbox %}

strict_variables setting

By default, Twig silently ignores undefined variables. While this is a good behavior for production environments, it makes debugging harder for developers. The strict_variables setting controls this behavior and is set to false by default. If enabled, Twig throws an exception when a variable is not defined.

Creating new Tags easily

When a developer want to create a new tag, he needs to understand many words: tokens, token streams, parsers, and compilers. Of course, he can just copy and paste existing code, but that’s far from perfect. As of Twig 0.9.7, there is a new high-level API to ease the creation of new tags. This is experimental and as the API can change, this is not yet documented.

Feel free to play with the new API and give us as much feedback as possible. To help you getting started faster, have a look at the code of the Symfony2 TwigBundle, which makes heavy use of the new API. You can start by looking at the TokenParser class and the Helpers classes.

Upgrading

Before upgrading, be sure to have read this post carefully as this release might break your existing code. Then, follow the standard steps:

  • Install the new version (via PEAR, SVN, Git, or by downloading the new package);

  • Remove all cached templates;

  • Test.

Living on the edge: Dynamic and conditional inheritance

The next version of Twig (0.9.7) is coming along quite nicely and it will have some very nice enhancements.

Today, I want to talk about template inheritance. Since the launch of Twig last year, the most asked feature is probably the possibility to have dynamic and/or conditional inheritance.

Up to Twig 0.9.6, the extends tag used for inheritance only supports static template names:

{% extends "layout.twig" %}

...

But what if you want to change the layout name according to some logic? Let’s say the layout depends on whether the request is an Ajax one or not. For Ajax requests, you want to change the layout to a “lighter” one. This will be possible in Twig 0.9.7 (not yet released).

The first way to have a layout depending on some variable is to pass the layout name as a variable to the template:

{% extends layout %}

Here the layout variable is a string containing the template name and its value has probably been chosen by a controller based on the X-Requested-With request header value.

But there is more. If you want to make the decision directly in the template itself, that’s also possible:

{% extends is_ajax ? 'layout_ajax.twig' : 'layout.twig' %}

As a matter of fact, the template name can now be any valid Twig expression (already supported by the include tag); so the following is also valid now:

{% extends 'layout' ~ extension %}

Instead of passing the template as a string, you can even pass a Twig_Template instance to the template:

// {% extends layout %}

$layout = $twig->loadTemplate('some_layout_template.twig');

$twig->display('template.twig', array('layout' => $layout));

Note that this possibility to use a Twig_Template instance was also added to the include tag:

// {% include template %}

$template = $twig->loadTemplate('some_template.twig');

$twig->display('template.twig', array('template' => $template));

Under the Hood

Supporting dynamic template inheritance was no easy task as Twig relied on PHP inheritance. And of course, it’s not possible to change the parent class dynamically in PHP:

$bar = 'SomeBaseClass';

class Foo extends $bar
{
    // ...
}

So, internally, Twig does not use PHP inheritance anymore and instead simulates it. Some quick benchmarks indicate that the overhead is negligible. And I’m sure it will open a lot more opportunities and flexibility.

If you are curious about the actual code, have a look at this changeset.

Twig 0.9.6 released

I have just released Twig 0.9.6. This is a maintenance release, where we fixed quite a few bugs and added some small features. We only have two major tickets to resolve before Twig 1.0.

Besides bug fixes and new features, there are two noticeable changes for Twig internals (with no impact for end users): we now use PHPUnit for unit tests, and PEAR coding standards.

The documentation has been updated to reflect the changes made in this new version.

The following sections talks about the main changes, but refer to the changelog if you want all the nitty-gritty details.

Upgrading

As there is no backward incompatibility, upgrading from 0.9.5 should be safe:

  • Install the new version (via PEAR, SVN, Git, or by downloading the new package);

  • Remove all cached templates;

  • Test.

Bug fixed

We fixed quite a few bugs for this release, but the main ones are the following:

  • fixed the Lexer when a template has a big chunk of text between/in a block;

  • fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII;

  • fixed node visiting for macros (macros are now visited by visitors as any other node);

  • fixed variables defined outside a loop and for which the value changes in a for loop.

New Features

i18n extension

You can now internationalize your templates thanks to the i18n extension. This extension only works if the PHP gettext extension is enabled.

Use the trans block to mark parts in the template as translatable:

{% trans %}
Hello World!
{% endtrans %}

You can embed variables if needed:

{% trans %}
Hello {{ name }}!
{% endtrans %}

To pluralize a translatable string, use the plural block:

{% trans apple_count %}
Hey {{ name }}, I have one apple.
{% plural %}
Hey {{ name }}, I have {{ count }} apples.
{% endtrans %}

__call() support

Twig is now able to use the __call() method during variable resolution.

The new algorithm for object method resolution is now the following:

  • get all methods declared for $object (using reflectionObject);

  • check if $item is in the list of method names;

  • check if get$item is in the list of method names;

  • check if __call is defined and pass $item as the method name.

set tag

The set tag now supports a long syntax. It’s quite useful when you need to ‘capture’ chunks of HTML:

{% set foo %}
  <div id="pagination">
    ...
  </div>
{% endset %}

cycle filter

The cycle filter can be used to cycle between an array of values:

{% for i in 0..10 %}
  {{ ['odd', 'even']|cycle(i) }}
{% endfor %}

The array can contain any number of values:

{% set fruits as ['apple', 'orange', 'citrus'] %}

{% for i in 0..10 %}
  {{ fruits|cycle(i) }}
{% endfor %}

Twig migration to Git

I have just switched from using Subversion as the main repository for Twig to Git.

This change is transparent for Twig users. Before, the Subversion repository was mirrored to Git every 15 minutes. Now, this is just the other way round. It means that I commit to Git instead of Subversion.

I have also migrated the ticketing system from Trac to Git.

Twig 0.9.5 released

I’m proud to announce the immediate availability of Twig 0.9.5.

Before upgrading from 0.9.4, read this post carefully as this new release might break your existing code.

The documentation has been updated to reflect the changes made in this new version. We have also enhanced the documentation based on the feedback we had.

New Features

.. Operator

One of the main frequently asked question was about iterating over a range of integer. Thanks to the new .. operator, this is now really easy to accomplish:

{% for i in 0..10 %}
  * {{ i }}
{% endfor %}

The above snippet of code prints all numbers from 0 to 9.

The left and right side of the .. operator can be any valid expression:

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

{% for letter in 'a'|upper..'z'|upper %}
  * {{ letter }}
{% endfor %}

Note that the .. operator is just syntactic sugar for the range filter. The first example is equivalent to the following:

{% for i in 0|range(10) %}
  * {{ i }}
{% endfor %}

Array Support

Twig supports a new literal: arrays. Arrays are defined by a sequence of expressions separated by a comma (,) and wrapped with squared brackets ([]). Arrays can also be nested as much as needed:

{% set foo as [a, 'b', ['c', 'd']] %}

Like in PHP, arrays are a mix between lists and dictionaries, arrays and hashes:

{% set foo as ['a': 'a', 'b': 'c'] %}

in Operator

The in operator performs a containment test. It returns true if the left operand is contained in the right one:

{{ 1 in [1, 2, 3] }}
{# returns true #}

{% if current_user in users %}
  {# do something #}
{% endif %}

To perform a negative test, the whole expression should be prefixed with not:

{{ not 1 in [1, 2, 3] }}
{# returns false #}

The in operator is just syntactic sugar for the in filter. The first example is equivalent to the following:

{{ 1|in([1, 2, 3]) }}

Better Filter System

The filter system has been rework to allow for more flexibility. For instance, You can now easily bundle your filters in your extension class:

class Project_Twig_Extension extends Twig_Extension
{
  public function getFilters()
  {
    return array(
      'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
    );
  }

  public function rot13Filter($string)
  {
    return str_rot13($string);
  }

  public function getName()
  {
    return 'project';
  }
}

include tag

You can now explicitly pass variables when including another template:

{% include 'foo' with ['foo': foo] %}

The with option can be combined with the sandboxed one to allow for very secure templates:

{% include 'foo' sandboxed with vars %}

Speed improvements

This new version of Twig is faster than any other Twig version. The speed gain depends on your templates of course.

For even more speed, you can now disable the automatic creation of the loop variable in a for loop:

{% for i in 0..10 without loop %}
  * {{ i }}
{% endfor %}

Miscellaneous

This version also has many other small improvements. The full changelog read as follows:

  • fixed list nodes that did not extend the Twig_NodeListInterface
  • added the “without loop” option to the for tag (it disables the generation of the loop variable)
  • refactored node transformers to node visitors
  • fixed automatic-escaping for blocks
  • added a way to specify variables to pass to an included template
  • changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules)
  • improved the filter system to allow object methods to be used as filters
  • changed the Array and String loaders to actually make use of the cache mechanism
  • included the default filter function definitions in the extension class files directly (Core, Escaper)
  • added the // operator (like the floor() PHP function)
  • added the .. operator (as a syntactic sugar for the range filter when the step is 1)
  • added the in operator (as a syntactic sugar for the in filter)
  • added the following filters in the Core extension: in, range
  • added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes)
  • enhanced some error messages to provide better feedback in case of parsing errors

Backwards Incompatibilities

Custom Filters

If you have defined custom filters, you MUST upgrade them for this release.

Before 0.9.4, filters were defined with a simple array like this:

// before
'even'   => array('twig_is_even_filter', false),
'escape' => array('twig_escape_filter', true),

As of 0.9.5, a filter is defined as an object and the needs_environment option replaces the Boolean:

// after
'even'   => new Twig_Filter_Function('twig_is_even_filter'),
'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),

NodeTransformers

If you have created NodeTransformer classes, you will need to upgrade them to the new interface. Read the built-in transformers classes to learn more about this topic.

Upgrading

Upgrading can be done in a few easy steps:

  • Install the new version (via PEAR, SVN, Git, or by downloading the new package);

  • Read the previous section about backward incompatibilities, and upgrade your code accordingly;

  • Remove all cached templates;

  • Test.

Twig 0.9.4 released

Twig 0.9.4 has just been released.

If you have custom loaders, you MUST upgrade them for this release: The Twig_Loader base class has been removed, and the Twig_LoaderInterface has also been changed (see the source code for more information or the online documentation, which has been updated).

The complete changelog reads as follow:

  • added support for DateTime instances for the date filter
  • fixed loop.last when the array only has one item
  • made it possible to insert newlines in tag and variable blocks
  • fixed a bug when a literal ‘\n’ were present in a template text
  • fixed bug when the filename of a template contains */
  • refactored loaders

Twig 0.9.3 released

I have just released Twig 0.9.3.

This release dramatically improves the performance of loops. It’s also the first backward incompatible release since the beginning of the project.

Loops are also much more easier to use as the items filters is not needed anymore:

{# before 0.9.3 #}
{% for k, v in values|items %}

{# as of 0.9.3 #}
{% for k, v in values %}

The previous template still work as items is now a noop filter. So, you can safely removed it

The loaders do not take the cache and autoReload arguments anymore. Instead, the Twig_Environment class has two new options: cache and auto_reload. Upgrading your code means changing this kind of code:

$loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true);
$twig = new Twig_Environment($loader);

to something like this:

$loader = new Twig_Loader_Filesystem('/path/to/templates');
$twig = new Twig_Environment($loader, array(
  'cache' => '/path/to/compilation_cache',
  'auto_reload' => true,
));

The complete changelog follows:

  • made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader
  • deprecated the “items” filter as it is not needed anymore
  • optimized template loading speed
  • removed output when an error occurs in a template and render() is used
  • made major speed improvements for loops (up to 300% on even the smallest loops)
  • added properties as part of the sandbox mode
  • added public properties support (obj.item can now be the item property on the obj object)
  • extended set tag to support expression as value ({% set foo as ‘foo’ ~ ‘bar’ %} )
  • fixed bug when \ was used in HTML

Twig 0.9.2 released

I have just released Twig 0.9.2. Lots of great changes occurred for this release:

  • made some speed optimizations
  • changed the cache extension to .php
  • added a js escaping strategy
  • added support for short block tag
  • changed the filter tag to allow chained filters
  • made lexer more flexible as you can now change the default delimiters
  • added set tag
  • changed default directory permission when cache dir does not exist (more secure)
  • added macro support
  • changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance
  • made Twig_Autoloader::autoload() a static method
  • avoid writing template file if an error occurs
  • added $ escaping when outputting raw strings
  • enhanced some error messages to ease debugging
  • fixed empty cache files when the template contains an error

Twig at the 2009 Zend Conference

Yesterday, I gave a talk about Twig at the Zend unconference. The slides are available at slideshare.

This website is powered by PHP and Twig.