4 .. _deprecation-notices:
6 Displaying Deprecation Notices
7 ------------------------------
10 This works as of Twig 1.21.
12 Deprecated features generate deprecation notices (via a call to the
13 ``trigger_error()`` PHP function). By default, they are silenced and never
16 To easily remove all deprecated feature usages from your templates, write and
17 run a script along the lines of the following::
19 require_once __DIR__.'/vendor/autoload.php';
21 $twig = create_your_twig_env();
23 $deprecations = new Twig_Util_DeprecationCollector($twig);
25 print_r($deprecations->collectDir(__DIR__.'/templates'));
27 The ``collectDir()`` method compiles all templates found in a directory,
28 catches deprecation notices, and return them.
32 If your templates are not stored on the filesystem, use the ``collect()``
33 method instead. ``collect()`` takes a ``Traversable`` which must return
34 template names as keys and template contents as values (as done by
35 ``Twig_Util_TemplateDirIterator``).
37 However, this code won't find all deprecations (like using deprecated some Twig
38 classes). To catch all notices, register a custom error handler like the one
41 $deprecations = array();
42 set_error_handler(function ($type, $msg) use (&$deprecations) {
43 if (E_USER_DEPRECATED === $type) {
44 $deprecations[] = $msg;
48 // run your application
50 print_r($deprecations);
52 Note that most deprecation notices are triggered during **compilation**, so
53 they won't be generated when templates are already cached.
57 If you want to manage the deprecation notices from your PHPUnit tests, have
58 a look at the `symfony/phpunit-bridge
59 <https://github.com/symfony/phpunit-bridge>`_ package, which eases the
62 Making a Layout conditional
63 ---------------------------
65 Working with Ajax means that the same content is sometimes displayed as is,
66 and sometimes decorated with a layout. As Twig layout template names can be
67 any valid expression, you can pass a variable that evaluates to ``true`` when
68 the request is made via Ajax and choose the layout accordingly:
72 {% extends request.ajax ? "base_ajax.html" : "base.html" %}
75 This is the content to be displayed.
78 Making an Include dynamic
79 -------------------------
81 When including a template, its name does not need to be a string. For
82 instance, the name can depend on the value of a variable:
86 {% include var ~ '_foo.html' %}
88 If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
91 As a matter of fact, the template name can be any valid expression, such as
96 {% include var|default('index') ~ '_foo.html' %}
98 Overriding a Template that also extends itself
99 ----------------------------------------------
101 A template can be customized in two different ways:
103 * *Inheritance*: A template *extends* a parent template and overrides some
106 * *Replacement*: If you use the filesystem loader, Twig loads the first
107 template it finds in a list of configured directories; a template found in a
108 directory *replaces* another one from a directory further in the list.
110 But how do you combine both: *replace* a template that also extends itself
111 (aka a template in a directory further in the list)?
113 Let's say that your templates are loaded from both ``.../templates/mysite``
114 and ``.../templates/default`` in this order. The ``page.twig`` template,
115 stored in ``.../templates/default`` reads as follows:
117 .. code-block:: jinja
120 {% extends "layout.twig" %}
125 You can replace this template by putting a file with the same name in
126 ``.../templates/mysite``. And if you want to extend the original template, you
127 might be tempted to write the following:
129 .. code-block:: jinja
131 {# page.twig in .../templates/mysite #}
132 {% extends "page.twig" %} {# from .../templates/default #}
134 Of course, this will not work as Twig will always load the template from
135 ``.../templates/mysite``.
137 It turns out it is possible to get this to work, by adding a directory right
138 at the end of your template directories, which is the parent of all of the
139 other directories: ``.../templates`` in our case. This has the effect of
140 making every template file within our system uniquely addressable. Most of the
141 time you will use the "normal" paths, but in the special case of wanting to
142 extend a template with an overriding version of itself we can reference its
143 parent's full, unambiguous template path in the extends tag:
145 .. code-block:: jinja
147 {# page.twig in .../templates/mysite #}
148 {% extends "default/page.twig" %} {# from .../templates #}
152 This recipe was inspired by the following Django wiki page:
153 https://code.djangoproject.com/wiki/ExtendingTemplates
155 Customizing the Syntax
156 ----------------------
158 Twig allows some syntax customization for the block delimiters. It's not
159 recommended to use this feature as templates will be tied with your custom
160 syntax. But for specific projects, it can make sense to change the defaults.
162 To change the block delimiters, you need to create your own lexer object::
164 $twig = new Twig_Environment();
166 $lexer = new Twig_Lexer($twig, array(
167 'tag_comment' => array('{#', '#}'),
168 'tag_block' => array('{%', '%}'),
169 'tag_variable' => array('{{', '}}'),
170 'interpolation' => array('#{', '}'),
172 $twig->setLexer($lexer);
174 Here are some configuration example that simulates some other template engines
178 $lexer = new Twig_Lexer($twig, array(
179 'tag_comment' => array('<%#', '%>'),
180 'tag_block' => array('<%', '%>'),
181 'tag_variable' => array('<%=', '%>'),
184 // SGML Comment Syntax
185 $lexer = new Twig_Lexer($twig, array(
186 'tag_comment' => array('<!--#', '-->'),
187 'tag_block' => array('<!--', '-->'),
188 'tag_variable' => array('${', '}'),
192 $lexer = new Twig_Lexer($twig, array(
193 'tag_comment' => array('{*', '*}'),
194 'tag_block' => array('{', '}'),
195 'tag_variable' => array('{$', '}'),
198 Using dynamic Object Properties
199 -------------------------------
201 When Twig encounters a variable like ``article.title``, it tries to find a
202 ``title`` public property in the ``article`` object.
204 It also works if the property does not exist but is rather defined dynamically
205 thanks to the magic ``__get()`` method; you just need to also implement the
206 ``__isset()`` magic method like shown in the following snippet of code::
210 public function __get($name)
212 if ('title' == $name) {
216 // throw some kind of error
219 public function __isset($name)
221 if ('title' == $name) {
229 Accessing the parent Context in Nested Loops
230 --------------------------------------------
232 Sometimes, when using nested loops, you need to access the parent context. The
233 parent context is always accessible via the ``loop.parent`` variable. For
234 instance, if you have the following template data::
238 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
239 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
243 And the following template to display all messages in all topics:
245 .. code-block:: jinja
247 {% for topic, messages in topics %}
248 * {{ loop.index }}: {{ topic }}
249 {% for message in messages %}
250 - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
254 The output will be similar to:
259 - 1.1: The message 1 of topic 1
260 - 1.2: The message 2 of topic 1
262 - 2.1: The message 1 of topic 2
263 - 2.2: The message 2 of topic 2
265 In the inner loop, the ``loop.parent`` variable is used to access the outer
266 context. So, the index of the current ``topic`` defined in the outer for loop
267 is accessible via the ``loop.parent.loop.index`` variable.
269 Defining undefined Functions and Filters on the Fly
270 ---------------------------------------------------
272 When a function (or a filter) is not defined, Twig defaults to throw a
273 ``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
274 valid PHP callable) which should return a function (or a filter).
276 For filters, register callbacks with ``registerUndefinedFilterCallback()``.
277 For functions, use ``registerUndefinedFunctionCallback()``::
279 // auto-register all native PHP functions as Twig functions
280 // don't try this at home as it's not secure at all!
281 $twig->registerUndefinedFunctionCallback(function ($name) {
282 if (function_exists($name)) {
283 return new Twig_SimpleFunction($name, $name);
289 If the callable is not able to return a valid function (or filter), it must
292 If you register more than one callback, Twig will call them in turn until one
293 does not return ``false``.
297 As the resolution of functions and filters is done during compilation,
298 there is no overhead when registering these callbacks.
300 Validating the Template Syntax
301 ------------------------------
303 When template code is provided by a third-party (through a web interface for
304 instance), it might be interesting to validate the template syntax before
305 saving it. If the template code is stored in a `$template` variable, here is
309 $twig->parse($twig->tokenize(new Twig_Source($template)));
311 // the $template is valid
312 } catch (Twig_Error_Syntax $e) {
313 // $template contains one or more syntax errors
316 If you iterate over a set of files, you can pass the filename to the
317 ``tokenize()`` method to get the filename in the exception message::
319 foreach ($files as $file) {
321 $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
323 // the $template is valid
324 } catch (Twig_Error_Syntax $e) {
325 // $template contains one or more syntax errors
329 .. versionadded:: 1.27
330 ``Twig_Source`` was introduced in version 1.27, pass the source and the
331 identifier directly on previous versions.
335 This method won't catch any sandbox policy violations because the policy
336 is enforced during template rendering (as Twig needs the context for some
337 checks like allowed methods on objects).
339 Refreshing modified Templates when OPcache or APC is enabled
340 ------------------------------------------------------------
342 When using OPcache with ``opcache.validate_timestamps`` set to ``0`` or APC
343 with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing the template
344 cache won't update the cache.
346 To get around this, force Twig to invalidate the bytecode cache::
348 $twig = new Twig_Environment($loader, array(
349 'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
355 Before Twig 1.22, you should extend ``Twig_Environment`` instead::
357 class OpCacheAwareTwigEnvironment extends Twig_Environment
359 protected function writeCacheFile($file, $content)
361 parent::writeCacheFile($file, $content);
363 // Compile cached file into bytecode cache
364 if (function_exists('opcache_invalidate')) {
365 opcache_invalidate($file, true);
366 } elseif (function_exists('apc_compile_file')) {
367 apc_compile_file($file);
372 Reusing a stateful Node Visitor
373 -------------------------------
375 When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
376 visit *all* templates it compiles. If you need to keep some state information
377 around, you probably want to reset it when visiting a new template.
379 This can be easily achieved with the following code::
381 protected $someTemplateState = array();
383 public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
385 if ($node instanceof Twig_Node_Module) {
386 // reset the state as we are entering a new template
387 $this->someTemplateState = array();
395 Using a Database to store Templates
396 -----------------------------------
398 If you are developing a CMS, templates are usually stored in a database. This
399 recipe gives you a simple PDO template loader you can use as a starting point
402 First, let's create a temporary in-memory SQLite3 database to work with::
404 $dbh = new PDO('sqlite::memory:');
405 $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
406 $base = '{% block content %}{% endblock %}';
408 {% extends "base.twig" %}
409 {% block content %}Hello {{ name }}{% endblock %}
412 $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
413 $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
415 We have created a simple ``templates`` table that hosts two templates:
416 ``base.twig`` and ``index.twig``.
418 Now, let's define a loader able to use this database::
420 class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
424 public function __construct(PDO $dbh)
429 public function getSource($name)
431 if (false === $source = $this->getValue('source', $name)) {
432 throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
438 // Twig_SourceContextLoaderInterface as of Twig 1.27
439 public function getSourceContext($name)
441 if (false === $source = $this->getValue('source', $name)) {
442 throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
445 return new Twig_Source($source, $name);
448 // Twig_ExistsLoaderInterface as of Twig 1.11
449 public function exists($name)
451 return $name === $this->getValue('name', $name);
454 public function getCacheKey($name)
459 public function isFresh($name, $time)
461 if (false === $lastModified = $this->getValue('last_modified', $name)) {
465 return $lastModified <= $time;
468 protected function getValue($column, $name)
470 $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
471 $sth->execute(array(':name' => (string) $name));
473 return $sth->fetchColumn();
477 Finally, here is an example on how you can use it::
479 $loader = new DatabaseTwigLoader($dbh);
480 $twig = new Twig_Environment($loader);
482 echo $twig->render('index.twig', array('name' => 'Fabien'));
484 Using different Template Sources
485 --------------------------------
487 This recipe is the continuation of the previous one. Even if you store the
488 contributed templates in a database, you might want to keep the original/base
489 templates on the filesystem. When templates can be loaded from different
490 sources, you need to use the ``Twig_Loader_Chain`` loader.
492 As you can see in the previous recipe, we reference the template in the exact
493 same way as we would have done it with a regular filesystem loader. This is
494 the key to be able to mix and match templates coming from the database, the
495 filesystem, or any other loader for that matter: the template name should be a
496 logical name, and not the path from the filesystem::
498 $loader1 = new DatabaseTwigLoader($dbh);
499 $loader2 = new Twig_Loader_Array(array(
500 'base.twig' => '{% block content %}{% endblock %}',
502 $loader = new Twig_Loader_Chain(array($loader1, $loader2));
504 $twig = new Twig_Environment($loader);
506 echo $twig->render('index.twig', array('name' => 'Fabien'));
508 Now that the ``base.twig`` templates is defined in an array loader, you can
509 remove it from the database, and everything else will still work as before.
511 Loading a Template from a String
512 --------------------------------
514 From a template, you can easily load a template stored in a string via the
515 ``template_from_string`` function (available as of Twig 1.11 via the
516 ``Twig_Extension_StringLoader`` extension):
518 .. code-block:: jinja
520 {{ include(template_from_string("Hello {{ name }}")) }}
522 From PHP, it's also possible to load a template stored in a string via
523 ``Twig_Environment::createTemplate()`` (available as of Twig 1.18)::
525 $template = $twig->createTemplate('hello {{ name }}');
526 echo $template->render(array('name' => 'Fabien'));
530 Never use the ``Twig_Loader_String`` loader, which has severe limitations.
532 Using Twig and AngularJS in the same Templates
533 ----------------------------------------------
535 Mixing different template syntaxes in the same file is not a recommended
536 practice as both AngularJS and Twig use the same delimiters in their syntax:
539 Still, if you want to use AngularJS and Twig in the same template, there are
540 two ways to make it work depending on the amount of AngularJS you need to
541 include in your templates:
543 * Escaping the AngularJS delimiters by wrapping AngularJS sections with the
544 ``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and
547 * Changing the delimiters of one of the template engines (depending on which
548 engine you introduced last):
550 * For AngularJS, change the interpolation tags using the
551 ``interpolateProvider`` service, for instance at the module initialization
554 .. code-block:: javascript
556 angular.module('myApp', []).config(function($interpolateProvider) {
557 $interpolateProvider.startSymbol('{[').endSymbol(']}');
560 * For Twig, change the delimiters via the ``tag_variable`` Lexer option:
564 $env->setLexer(new Twig_Lexer($env, array(
565 'tag_variable' => array('{[', ']}'),
568 .. _callback: https://secure.php.net/manual/en/function.is-callable.php