b3ba7f4d7bc60cfb382e1a19d7377d12d6a016c5
[yaffs-website] / vendor / twig / twig / doc / recipes.rst
1 Recipes
2 =======
3
4 .. _deprecation-notices:
5
6 Displaying Deprecation Notices
7 ------------------------------
8
9 .. versionadded:: 1.21
10     This works as of Twig 1.21.
11
12 Deprecated features generate deprecation notices (via a call to the
13 ``trigger_error()`` PHP function). By default, they are silenced and never
14 displayed nor logged.
15
16 To easily remove all deprecated feature usages from your templates, write and
17 run a script along the lines of the following::
18
19     require_once __DIR__.'/vendor/autoload.php';
20
21     $twig = create_your_twig_env();
22
23     $deprecations = new Twig_Util_DeprecationCollector($twig);
24
25     print_r($deprecations->collectDir(__DIR__.'/templates'));
26
27 The ``collectDir()`` method compiles all templates found in a directory,
28 catches deprecation notices, and return them.
29
30 .. tip::
31
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``).
36
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
39 below::
40
41     $deprecations = array();
42     set_error_handler(function ($type, $msg) use (&$deprecations) {
43         if (E_USER_DEPRECATED === $type) {
44             $deprecations[] = $msg;
45         }
46     });
47
48     // run your application
49
50     print_r($deprecations);
51
52 Note that most deprecation notices are triggered during **compilation**, so
53 they won't be generated when templates are already cached.
54
55 .. tip::
56
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
60     process a lot.
61
62 Making a Layout conditional
63 ---------------------------
64
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:
69
70 .. code-block:: jinja
71
72     {% extends request.ajax ? "base_ajax.html" : "base.html" %}
73
74     {% block content %}
75         This is the content to be displayed.
76     {% endblock %}
77
78 Making an Include dynamic
79 -------------------------
80
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:
83
84 .. code-block:: jinja
85
86     {% include var ~ '_foo.html' %}
87
88 If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
89 rendered.
90
91 As a matter of fact, the template name can be any valid expression, such as
92 the following:
93
94 .. code-block:: jinja
95
96     {% include var|default('index') ~ '_foo.html' %}
97
98 Overriding a Template that also extends itself
99 ----------------------------------------------
100
101 A template can be customized in two different ways:
102
103 * *Inheritance*: A template *extends* a parent template and overrides some
104   blocks;
105
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.
109
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)?
112
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:
116
117 .. code-block:: jinja
118
119     {# page.twig #}
120     {% extends "layout.twig" %}
121
122     {% block content %}
123     {% endblock %}
124
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:
128
129 .. code-block:: jinja
130
131     {# page.twig in .../templates/mysite #}
132     {% extends "page.twig" %} {# from .../templates/default #}
133
134 Of course, this will not work as Twig will always load the template from
135 ``.../templates/mysite``.
136
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:
144
145 .. code-block:: jinja
146
147     {# page.twig in .../templates/mysite #}
148     {% extends "default/page.twig" %} {# from .../templates #}
149
150 .. note::
151
152     This recipe was inspired by the following Django wiki page:
153     http://code.djangoproject.com/wiki/ExtendingTemplates
154
155 Customizing the Syntax
156 ----------------------
157
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.
161
162 To change the block delimiters, you need to create your own lexer object::
163
164     $twig = new Twig_Environment();
165
166     $lexer = new Twig_Lexer($twig, array(
167         'tag_comment'   => array('{#', '#}'),
168         'tag_block'     => array('{%', '%}'),
169         'tag_variable'  => array('{{', '}}'),
170         'interpolation' => array('#{', '}'),
171     ));
172     $twig->setLexer($lexer);
173
174 Here are some configuration example that simulates some other template engines
175 syntax::
176
177     // Ruby erb syntax
178     $lexer = new Twig_Lexer($twig, array(
179         'tag_comment'  => array('<%#', '%>'),
180         'tag_block'    => array('<%', '%>'),
181         'tag_variable' => array('<%=', '%>'),
182     ));
183
184     // SGML Comment Syntax
185     $lexer = new Twig_Lexer($twig, array(
186         'tag_comment'  => array('<!--#', '-->'),
187         'tag_block'    => array('<!--', '-->'),
188         'tag_variable' => array('${', '}'),
189     ));
190
191     // Smarty like
192     $lexer = new Twig_Lexer($twig, array(
193         'tag_comment'  => array('{*', '*}'),
194         'tag_block'    => array('{', '}'),
195         'tag_variable' => array('{$', '}'),
196     ));
197
198 Using dynamic Object Properties
199 -------------------------------
200
201 When Twig encounters a variable like ``article.title``, it tries to find a
202 ``title`` public property in the ``article`` object.
203
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::
207
208     class Article
209     {
210         public function __get($name)
211         {
212             if ('title' == $name) {
213                 return 'The title';
214             }
215
216             // throw some kind of error
217         }
218
219         public function __isset($name)
220         {
221             if ('title' == $name) {
222                 return true;
223             }
224
225             return false;
226         }
227     }
228
229 Accessing the parent Context in Nested Loops
230 --------------------------------------------
231
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::
235
236     $data = array(
237         'topics' => array(
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'),
240         ),
241     );
242
243 And the following template to display all messages in all topics:
244
245 .. code-block:: jinja
246
247     {% for topic, messages in topics %}
248         * {{ loop.index }}: {{ topic }}
249       {% for message in messages %}
250           - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
251       {% endfor %}
252     {% endfor %}
253
254 The output will be similar to:
255
256 .. code-block:: text
257
258     * 1: topic1
259       - 1.1: The message 1 of topic 1
260       - 1.2: The message 2 of topic 1
261     * 2: topic2
262       - 2.1: The message 1 of topic 2
263       - 2.2: The message 2 of topic 2
264
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.
268
269 Defining undefined Functions and Filters on the Fly
270 ---------------------------------------------------
271
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).
275
276 For filters, register callbacks with ``registerUndefinedFilterCallback()``.
277 For functions, use ``registerUndefinedFunctionCallback()``::
278
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);
284         }
285
286         return false;
287     });
288
289 If the callable is not able to return a valid function (or filter), it must
290 return ``false``.
291
292 If you register more than one callback, Twig will call them in turn until one
293 does not return ``false``.
294
295 .. tip::
296
297     As the resolution of functions and filters is done during compilation,
298     there is no overhead when registering these callbacks.
299
300 Validating the Template Syntax
301 ------------------------------
302
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
306 how you can do it::
307
308     try {
309         $twig->parse($twig->tokenize(new Twig_Source($template)));
310
311         // the $template is valid
312     } catch (Twig_Error_Syntax $e) {
313         // $template contains one or more syntax errors
314     }
315
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::
318
319     foreach ($files as $file) {
320         try {
321             $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
322
323             // the $template is valid
324         } catch (Twig_Error_Syntax $e) {
325             // $template contains one or more syntax errors
326         }
327     }
328
329 .. versionadded:: 1.27
330     ``Twig_Source`` was introduced in version 1.27, pass the source and the
331     identifier directly on previous versions.
332
333 .. note::
334
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).
338
339 Refreshing modified Templates when OPcache or APC is enabled
340 ------------------------------------------------------------
341
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.
345
346 To get around this, force Twig to invalidate the bytecode cache::
347
348     $twig = new Twig_Environment($loader, array(
349         'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
350         // ...
351     ));
352
353 .. note::
354
355     Before Twig 1.22, you should extend ``Twig_Environment`` instead::
356
357         class OpCacheAwareTwigEnvironment extends Twig_Environment
358         {
359             protected function writeCacheFile($file, $content)
360             {
361                 parent::writeCacheFile($file, $content);
362
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);
368                 }
369             }
370         }
371
372 Reusing a stateful Node Visitor
373 -------------------------------
374
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.
378
379 This can be easily achieved with the following code::
380
381     protected $someTemplateState = array();
382
383     public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
384     {
385         if ($node instanceof Twig_Node_Module) {
386             // reset the state as we are entering a new template
387             $this->someTemplateState = array();
388         }
389
390         // ...
391
392         return $node;
393     }
394
395 Using a Database to store Templates
396 -----------------------------------
397
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
400 for your own.
401
402 First, let's create a temporary in-memory SQLite3 database to work with::
403
404     $dbh = new PDO('sqlite::memory:');
405     $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
406     $base = '{% block content %}{% endblock %}';
407     $index = '
408     {% extends "base.twig" %}
409     {% block content %}Hello {{ name }}{% endblock %}
410     ';
411     $now = time();
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)");
414
415 We have created a simple ``templates`` table that hosts two templates:
416 ``base.twig`` and ``index.twig``.
417
418 Now, let's define a loader able to use this database::
419
420     class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
421     {
422         protected $dbh;
423
424         public function __construct(PDO $dbh)
425         {
426             $this->dbh = $dbh;
427         }
428
429         public function getSource($name)
430         {
431             if (false === $source = $this->getValue('source', $name)) {
432                 throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
433             }
434
435             return $source;
436         }
437
438         // Twig_SourceContextLoaderInterface as of Twig 1.27
439         public function getSourceContext($name)
440         {
441             if (false === $source = $this->getValue('source', $name)) {
442                 throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
443             }
444
445             return new Twig_Source($source, $name);
446         }
447
448         // Twig_ExistsLoaderInterface as of Twig 1.11
449         public function exists($name)
450         {
451             return $name === $this->getValue('name', $name);
452         }
453
454         public function getCacheKey($name)
455         {
456             return $name;
457         }
458
459         public function isFresh($name, $time)
460         {
461             if (false === $lastModified = $this->getValue('last_modified', $name)) {
462                 return false;
463             }
464
465             return $lastModified <= $time;
466         }
467
468         protected function getValue($column, $name)
469         {
470             $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
471             $sth->execute(array(':name' => (string) $name));
472
473             return $sth->fetchColumn();
474         }
475     }
476
477 Finally, here is an example on how you can use it::
478
479     $loader = new DatabaseTwigLoader($dbh);
480     $twig = new Twig_Environment($loader);
481
482     echo $twig->render('index.twig', array('name' => 'Fabien'));
483
484 Using different Template Sources
485 --------------------------------
486
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.
491
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::
497
498     $loader1 = new DatabaseTwigLoader($dbh);
499     $loader2 = new Twig_Loader_Array(array(
500         'base.twig' => '{% block content %}{% endblock %}',
501     ));
502     $loader = new Twig_Loader_Chain(array($loader1, $loader2));
503
504     $twig = new Twig_Environment($loader);
505
506     echo $twig->render('index.twig', array('name' => 'Fabien'));
507
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.
510
511 Loading a Template from a String
512 --------------------------------
513
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):
517
518 .. code-block:: jinja
519
520     {{ include(template_from_string("Hello {{ name }}")) }}
521
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)::
524
525     $template = $twig->createTemplate('hello {{ name }}');
526     echo $template->render(array('name' => 'Fabien'));
527
528 .. note::
529
530     Never use the ``Twig_Loader_String`` loader, which has severe limitations.
531
532 Using Twig and AngularJS in the same Templates
533 ----------------------------------------------
534
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:
537 ``{{`` and ``}}``.
538
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:
542
543 * Escaping the AngularJS delimiters by wrapping AngularJS sections with the
544   ``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and
545   ``{{ '}}' }}``;
546
547 * Changing the delimiters of one of the template engines (depending on which
548   engine you introduced last):
549
550   * For AngularJS, change the interpolation tags using the
551     ``interpolateProvider`` service, for instance at the module initialization
552     time:
553
554     ..  code-block:: javascript
555
556         angular.module('myApp', []).config(function($interpolateProvider) {
557             $interpolateProvider.startSymbol('{[').endSymbol(']}');
558         });
559
560   * For Twig, change the delimiters via the ``tag_variable`` Lexer option:
561
562     ..  code-block:: php
563
564         $env->setLexer(new Twig_Lexer($env, array(
565             'tag_variable' => array('{[', ']}'),
566         )));
567
568 .. _callback: http://www.php.net/manual/en/function.is-callable.php