]> git.immae.eu Git - github/wallabag/wallabag.git/blame - vendor/twig/twig/doc/advanced.rst
gitignore vendor
[github/wallabag/wallabag.git] / vendor / twig / twig / doc / advanced.rst
CommitLineData
4f5b44bd
NL
1Extending Twig
2==============
3
4.. caution::
5
6 This section describes how to extend Twig as of **Twig 1.12**. If you are
7 using an older version, read the :doc:`legacy<advanced_legacy>` chapter
8 instead.
9
10Twig can be extended in many ways; you can add extra tags, filters, tests,
11operators, global variables, and functions. You can even extend the parser
12itself with node visitors.
13
14.. note::
15
16 The first section of this chapter describes how to extend Twig easily. If
17 you want to reuse your changes in different projects or if you want to
18 share them with others, you should then create an extension as described
19 in the following section.
20
21.. caution::
22
23 When extending Twig without creating an extension, Twig won't be able to
24 recompile your templates when the PHP code is updated. To see your changes
25 in real-time, either disable template caching or package your code into an
26 extension (see the next section of this chapter).
27
28Before extending Twig, you must understand the differences between all the
29different possible extension points and when to use them.
30
31First, remember that Twig has two main language constructs:
32
33* ``{{ }}``: used to print the result of an expression evaluation;
34
35* ``{% %}``: used to execute statements.
36
37To understand why Twig exposes so many extension points, let's see how to
38implement a *Lorem ipsum* generator (it needs to know the number of words to
39generate).
40
41You can use a ``lipsum`` *tag*:
42
43.. code-block:: jinja
44
45 {% lipsum 40 %}
46
47That works, but using a tag for ``lipsum`` is not a good idea for at least
48three main reasons:
49
50* ``lipsum`` is not a language construct;
51* The tag outputs something;
52* The tag is not flexible as you cannot use it in an expression:
53
54 .. code-block:: jinja
55
56 {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
57
58In fact, you rarely need to create tags; and that's good news because tags are
59the most complex extension point of Twig.
60
61Now, let's use a ``lipsum`` *filter*:
62
63.. code-block:: jinja
64
65 {{ 40|lipsum }}
66
67Again, it works, but it looks weird. A filter transforms the passed value to
68something else but here we use the value to indicate the number of words to
69generate (so, ``40`` is an argument of the filter, not the value we want to
70transform).
71
72Next, let's use a ``lipsum`` *function*:
73
74.. code-block:: jinja
75
76 {{ lipsum(40) }}
77
78Here we go. For this specific example, the creation of a function is the
79extension point to use. And you can use it anywhere an expression is accepted:
80
81.. code-block:: jinja
82
83 {{ 'some text' ~ lipsum(40) ~ 'some more text' }}
84
85 {% set lipsum = lipsum(40) %}
86
87Last but not the least, you can also use a *global* object with a method able
88to generate lorem ipsum text:
89
90.. code-block:: jinja
91
92 {{ text.lipsum(40) }}
93
94As a rule of thumb, use functions for frequently used features and global
95objects for everything else.
96
97Keep in mind the following when you want to extend Twig:
98
99========== ========================== ========== =========================
100What? Implementation difficulty? How often? When?
101========== ========================== ========== =========================
102*macro* trivial frequent Content generation
103*global* trivial frequent Helper object
104*function* trivial frequent Content generation
105*filter* trivial frequent Value transformation
106*tag* complex rare DSL language construct
107*test* trivial rare Boolean decision
108*operator* trivial rare Values transformation
109========== ========================== ========== =========================
110
111Globals
112-------
113
114A global variable is like any other template variable, except that it's
115available in all templates and macros::
116
117 $twig = new Twig_Environment($loader);
118 $twig->addGlobal('text', new Text());
119
120You can then use the ``text`` variable anywhere in a template:
121
122.. code-block:: jinja
123
124 {{ text.lipsum(40) }}
125
126Filters
127-------
128
129Creating a filter is as simple as associating a name with a PHP callable::
130
131 // an anonymous function
132 $filter = new Twig_SimpleFilter('rot13', function ($string) {
133 return str_rot13($string);
134 });
135
136 // or a simple PHP function
137 $filter = new Twig_SimpleFilter('rot13', 'str_rot13');
138
139 // or a class method
140 $filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
141
142The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
143of the filter you will use in templates and the second one is the PHP callable
144to associate with it.
145
146Then, add the filter to your Twig environment::
147
148 $twig = new Twig_Environment($loader);
149 $twig->addFilter($filter);
150
151And here is how to use it in a template:
152
153.. code-block:: jinja
154
155 {{ 'Twig'|rot13 }}
156
157 {# will output Gjvt #}
158
159When called by Twig, the PHP callable receives the left side of the filter
160(before the pipe ``|``) as the first argument and the extra arguments passed
161to the filter (within parentheses ``()``) as extra arguments.
162
163For instance, the following code:
164
165.. code-block:: jinja
166
167 {{ 'TWIG'|lower }}
168 {{ now|date('d/m/Y') }}
169
170is compiled to something like the following::
171
172 <?php echo strtolower('TWIG') ?>
173 <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
174
175The ``Twig_SimpleFilter`` class takes an array of options as its last
176argument::
177
178 $filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
179
180Environment aware Filters
181~~~~~~~~~~~~~~~~~~~~~~~~~
182
183If you want to access the current environment instance in your filter, set the
184``needs_environment`` option to ``true``; Twig will pass the current
185environment as the first argument to the filter call::
186
187 $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $string) {
188 // get the current charset for instance
189 $charset = $env->getCharset();
190
191 return str_rot13($string);
192 }, array('needs_environment' => true));
193
194Context aware Filters
195~~~~~~~~~~~~~~~~~~~~~
196
197If you want to access the current context in your filter, set the
198``needs_context`` option to ``true``; Twig will pass the current context as
199the first argument to the filter call (or the second one if
200``needs_environment`` is also set to ``true``)::
201
202 $filter = new Twig_SimpleFilter('rot13', function ($context, $string) {
203 // ...
204 }, array('needs_context' => true));
205
206 $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $context, $string) {
207 // ...
208 }, array('needs_context' => true, 'needs_environment' => true));
209
210Automatic Escaping
211~~~~~~~~~~~~~~~~~~
212
213If automatic escaping is enabled, the output of the filter may be escaped
214before printing. If your filter acts as an escaper (or explicitly outputs html
215or JavaScript code), you will want the raw output to be printed. In such a
216case, set the ``is_safe`` option::
217
218 $filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
219
220Some filters may need to work on input that is already escaped or safe, for
221example when adding (safe) html tags to originally unsafe output. In such a
222case, set the ``pre_escape`` option to escape the input data before it is run
223through your filter::
224
225 $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
226
227Dynamic Filters
228~~~~~~~~~~~~~~~
229
230A filter name containing the special ``*`` character is a dynamic filter as
231the ``*`` can be any string::
232
233 $filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) {
234 // ...
235 });
236
237The following filters will be matched by the above defined dynamic filter:
238
239* ``product_path``
240* ``category_path``
241
242A dynamic filter can define more than one dynamic parts::
243
244 $filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {
245 // ...
246 });
247
248The filter will receive all dynamic part values before the normal filter
249arguments, but after the environment and the context. For instance, a call to
250``'foo'|a_path_b()`` will result in the following arguments to be passed to
251the filter: ``('a', 'b', 'foo')``.
252
253Functions
254---------
255
256Functions are defined in the exact same way as filters, but you need to create
257an instance of ``Twig_SimpleFunction``::
258
259 $twig = new Twig_Environment($loader);
260 $function = new Twig_SimpleFunction('function_name', function () {
261 // ...
262 });
263 $twig->addFunction($function);
264
265Functions support the same features as filters, except for the ``pre_escape``
266and ``preserves_safety`` options.
267
268Tests
269-----
270
271Tests are defined in the exact same way as filters and functions, but you need
272to create an instance of ``Twig_SimpleTest``::
273
274 $twig = new Twig_Environment($loader);
275 $test = new Twig_SimpleTest('test_name', function () {
276 // ...
277 });
278 $twig->addTest($test);
279
280Tests allow you to create custom application specific logic for evaluating
281boolean conditions. As a simple, example let's create a Twig test that checks if
282objects are 'red'::
283
284 $twig = new Twig_Environment($loader)
285 $test = new Twig_SimpleTest('red', function ($value) {
286 if (isset($value->color) && $value->color == 'red') {
287 return true;
288 }
289 if (isset($value->paint) && $value->paint == 'red') {
290 return true;
291 }
292 return false;
293 });
294 $twig->addTest($test);
295
296Test functions should always return true/false.
297
298When creating tests you can use the ``node_class`` option to provide custom test
299compilation. This is useful if your test can be compiled into PHP primitives.
300This is used by many of the tests built into Twig::
301
302 $twig = new Twig_Environment($loader)
303 $test = new Twig_SimpleTest(
304 'odd',
305 null,
306 array('node_class' => 'Twig_Node_Expression_Test_Odd'));
307 $twig->addTest($test);
308
309 class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
310 {
311 public function compile(Twig_Compiler $compiler)
312 {
313 $compiler
314 ->raw('(')
315 ->subcompile($this->getNode('node'))
316 ->raw(' % 2 == 1')
317 ->raw(')')
318 ;
319 }
320 }
321
322The above example, shows how you can create tests that use a node class. The
323node class has access to one sub-node called 'node'. This sub-node contains the
324value that is being tested. When the ``odd`` filter is used in code like:
325
326.. code-block:: jinja
327
328 {% if my_value is odd %}
329
330The ``node`` sub-node will contain an expression of ``my_value``. Node based
331tests also have access to the ``arguments`` node. This node will contain the
332various other arguments that have been provided to your test.
333
334Tags
335----
336
337One of the most exciting feature of a template engine like Twig is the
338possibility to define new language constructs. This is also the most complex
339feature as you need to understand how Twig's internals work.
340
341Let's create a simple ``set`` tag that allows the definition of simple
342variables from within a template. The tag can be used like follows:
343
344.. code-block:: jinja
345
346 {% set name = "value" %}
347
348 {{ name }}
349
350 {# should output value #}
351
352.. note::
353
354 The ``set`` tag is part of the Core extension and as such is always
355 available. The built-in version is slightly more powerful and supports
356 multiple assignments by default (cf. the template designers chapter for
357 more information).
358
359Three steps are needed to define a new tag:
360
361* Defining a Token Parser class (responsible for parsing the template code);
362
363* Defining a Node class (responsible for converting the parsed code to PHP);
364
365* Registering the tag.
366
367Registering a new tag
368~~~~~~~~~~~~~~~~~~~~~
369
370Adding a tag is as simple as calling the ``addTokenParser`` method on the
371``Twig_Environment`` instance::
372
373 $twig = new Twig_Environment($loader);
374 $twig->addTokenParser(new Project_Set_TokenParser());
375
376Defining a Token Parser
377~~~~~~~~~~~~~~~~~~~~~~~
378
379Now, let's see the actual code of this class::
380
381 class Project_Set_TokenParser extends Twig_TokenParser
382 {
383 public function parse(Twig_Token $token)
384 {
385 $parser = $this->parser;
386 $stream = $parser->getStream();
387
388 $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
389 $stream->expect(Twig_Token::OPERATOR_TYPE, '=');
390 $value = $parser->getExpressionParser()->parseExpression();
391 $stream->expect(Twig_Token::BLOCK_END_TYPE);
392
393 return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
394 }
395
396 public function getTag()
397 {
398 return 'set';
399 }
400 }
401
402The ``getTag()`` method must return the tag we want to parse, here ``set``.
403
404The ``parse()`` method is invoked whenever the parser encounters a ``set``
405tag. It should return a ``Twig_Node`` instance that represents the node (the
406``Project_Set_Node`` calls creating is explained in the next section).
407
408The parsing process is simplified thanks to a bunch of methods you can call
409from the token stream (``$this->parser->getStream()``):
410
411* ``getCurrent()``: Gets the current token in the stream.
412
413* ``next()``: Moves to the next token in the stream, *but returns the old one*.
414
415* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
416 the current token is of a particular type or value (or both). The value may be an
417 array of several possible values.
418
419* ``expect($type[, $value[, $message]])``: If the current token isn't of the given
420 type/value a syntax error is thrown. Otherwise, if the type and value are correct,
421 the token is returned and the stream moves to the next token.
422
423* ``look()``: Looks a the next token without consuming it.
424
425Parsing expressions is done by calling the ``parseExpression()`` like we did for
426the ``set`` tag.
427
428.. tip::
429
430 Reading the existing ``TokenParser`` classes is the best way to learn all
431 the nitty-gritty details of the parsing process.
432
433Defining a Node
434~~~~~~~~~~~~~~~
435
436The ``Project_Set_Node`` class itself is rather simple::
437
438 class Project_Set_Node extends Twig_Node
439 {
440 public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
441 {
442 parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
443 }
444
445 public function compile(Twig_Compiler $compiler)
446 {
447 $compiler
448 ->addDebugInfo($this)
449 ->write('$context[\''.$this->getAttribute('name').'\'] = ')
450 ->subcompile($this->getNode('value'))
451 ->raw(";\n")
452 ;
453 }
454 }
455
456The compiler implements a fluid interface and provides methods that helps the
457developer generate beautiful and readable PHP code:
458
459* ``subcompile()``: Compiles a node.
460
461* ``raw()``: Writes the given string as is.
462
463* ``write()``: Writes the given string by adding indentation at the beginning
464 of each line.
465
466* ``string()``: Writes a quoted string.
467
468* ``repr()``: Writes a PHP representation of a given value (see
469 ``Twig_Node_For`` for a usage example).
470
471* ``addDebugInfo()``: Adds the line of the original template file related to
472 the current node as a comment.
473
474* ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
475 usage example).
476
477* ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
478 usage example).
479
480.. _creating_extensions:
481
482Creating an Extension
483---------------------
484
485The main motivation for writing an extension is to move often used code into a
486reusable class like adding support for internationalization. An extension can
487define tags, filters, tests, operators, global variables, functions, and node
488visitors.
489
490Creating an extension also makes for a better separation of code that is
491executed at compilation time and code needed at runtime. As such, it makes
492your code faster.
493
494Most of the time, it is useful to create a single extension for your project,
495to host all the specific tags and filters you want to add to Twig.
496
497.. tip::
498
499 When packaging your code into an extension, Twig is smart enough to
500 recompile your templates whenever you make a change to it (when
501 ``auto_reload`` is enabled).
502
503.. note::
504
505 Before writing your own extensions, have a look at the Twig official
506 extension repository: http://github.com/fabpot/Twig-extensions.
507
508An extension is a class that implements the following interface::
509
510 interface Twig_ExtensionInterface
511 {
512 /**
513 * Initializes the runtime environment.
514 *
515 * This is where you can load some file that contains filter functions for instance.
516 *
517 * @param Twig_Environment $environment The current Twig_Environment instance
518 */
519 function initRuntime(Twig_Environment $environment);
520
521 /**
522 * Returns the token parser instances to add to the existing list.
523 *
524 * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
525 */
526 function getTokenParsers();
527
528 /**
529 * Returns the node visitor instances to add to the existing list.
530 *
531 * @return array An array of Twig_NodeVisitorInterface instances
532 */
533 function getNodeVisitors();
534
535 /**
536 * Returns a list of filters to add to the existing list.
537 *
538 * @return array An array of filters
539 */
540 function getFilters();
541
542 /**
543 * Returns a list of tests to add to the existing list.
544 *
545 * @return array An array of tests
546 */
547 function getTests();
548
549 /**
550 * Returns a list of functions to add to the existing list.
551 *
552 * @return array An array of functions
553 */
554 function getFunctions();
555
556 /**
557 * Returns a list of operators to add to the existing list.
558 *
559 * @return array An array of operators
560 */
561 function getOperators();
562
563 /**
564 * Returns a list of global variables to add to the existing list.
565 *
566 * @return array An array of global variables
567 */
568 function getGlobals();
569
570 /**
571 * Returns the name of the extension.
572 *
573 * @return string The extension name
574 */
575 function getName();
576 }
577
578To keep your extension class clean and lean, it can inherit from the built-in
579``Twig_Extension`` class instead of implementing the whole interface. That
580way, you just need to implement the ``getName()`` method as the
581``Twig_Extension`` provides empty implementations for all other methods.
582
583The ``getName()`` method must return a unique identifier for your extension.
584
585Now, with this information in mind, let's create the most basic extension
586possible::
587
588 class Project_Twig_Extension extends Twig_Extension
589 {
590 public function getName()
591 {
592 return 'project';
593 }
594 }
595
596.. note::
597
598 Of course, this extension does nothing for now. We will customize it in
599 the next sections.
600
601Twig does not care where you save your extension on the filesystem, as all
602extensions must be registered explicitly to be available in your templates.
603
604You can register an extension by using the ``addExtension()`` method on your
605main ``Environment`` object::
606
607 $twig = new Twig_Environment($loader);
608 $twig->addExtension(new Project_Twig_Extension());
609
610Of course, you need to first load the extension file by either using
611``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
612
613.. tip::
614
615 The bundled extensions are great examples of how extensions work.
616
617Globals
618~~~~~~~
619
620Global variables can be registered in an extension via the ``getGlobals()``
621method::
622
623 class Project_Twig_Extension extends Twig_Extension
624 {
625 public function getGlobals()
626 {
627 return array(
628 'text' => new Text(),
629 );
630 }
631
632 // ...
633 }
634
635Functions
636~~~~~~~~~
637
638Functions can be registered in an extension via the ``getFunctions()``
639method::
640
641 class Project_Twig_Extension extends Twig_Extension
642 {
643 public function getFunctions()
644 {
645 return array(
646 new Twig_SimpleFunction('lipsum', 'generate_lipsum'),
647 );
648 }
649
650 // ...
651 }
652
653Filters
654~~~~~~~
655
656To add a filter to an extension, you need to override the ``getFilters()``
657method. This method must return an array of filters to add to the Twig
658environment::
659
660 class Project_Twig_Extension extends Twig_Extension
661 {
662 public function getFilters()
663 {
664 return array(
665 new Twig_SimpleFilter('rot13', 'str_rot13'),
666 );
667 }
668
669 // ...
670 }
671
672Tags
673~~~~
674
675Adding a tag in an extension can be done by overriding the
676``getTokenParsers()`` method. This method must return an array of tags to add
677to the Twig environment::
678
679 class Project_Twig_Extension extends Twig_Extension
680 {
681 public function getTokenParsers()
682 {
683 return array(new Project_Set_TokenParser());
684 }
685
686 // ...
687 }
688
689In the above code, we have added a single new tag, defined by the
690``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
691responsible for parsing the tag and compiling it to PHP.
692
693Operators
694~~~~~~~~~
695
696The ``getOperators()`` methods allows to add new operators. Here is how to add
697``!``, ``||``, and ``&&`` operators::
698
699 class Project_Twig_Extension extends Twig_Extension
700 {
701 public function getOperators()
702 {
703 return array(
704 array(
705 '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
706 ),
707 array(
708 '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
709 '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
710 ),
711 );
712 }
713
714 // ...
715 }
716
717Tests
718~~~~~
719
720The ``getTests()`` methods allows to add new test functions::
721
722 class Project_Twig_Extension extends Twig_Extension
723 {
724 public function getTests()
725 {
726 return array(
727 new Twig_SimpleTest('even', 'twig_test_even'),
728 );
729 }
730
731 // ...
732 }
733
734Overloading
735-----------
736
737To overload an already defined filter, test, operator, global variable, or
738function, define it again **as late as possible**::
739
740 $twig = new Twig_Environment($loader);
741 $twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
742 // do something different from the built-in date filter
743 }));
744
745Here, we have overloaded the built-in ``date`` filter with a custom one.
746
747That also works with an extension::
748
749 class MyCoreExtension extends Twig_Extension
750 {
751 public function getFilters()
752 {
753 return array(
754 new Twig_SimpleFilter('date', array($this, 'dateFilter')),
755 );
756 }
757
758 public function dateFilter($timestamp, $format = 'F j, Y H:i')
759 {
760 // do something different from the built-in date filter
761 }
762
763 public function getName()
764 {
765 return 'project';
766 }
767 }
768
769 $twig = new Twig_Environment($loader);
770 $twig->addExtension(new MyCoreExtension());
771
772.. caution::
773
774 Note that overloading the built-in Twig elements is not recommended as it
775 might be confusing.
776
777Testing an Extension
778--------------------
779
780Functional Tests
781~~~~~~~~~~~~~~~~
782
783You can create functional tests for extensions simply by creating the
784following file structure in your test directory::
785
786 Fixtures/
787 filters/
788 foo.test
789 bar.test
790 functions/
791 foo.test
792 bar.test
793 tags/
794 foo.test
795 bar.test
796 IntegrationTest.php
797
798The ``IntegrationTest.php`` file should look like this::
799
800 class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
801 {
802 public function getExtensions()
803 {
804 return array(
805 new Project_Twig_Extension1(),
806 new Project_Twig_Extension2(),
807 );
808 }
809
810 public function getFixturesDir()
811 {
812 return dirname(__FILE__).'/Fixtures/';
813 }
814 }
815
816Fixtures examples can be found within the Twig repository
817`tests/Twig/Fixtures`_ directory.
818
819Node Tests
820~~~~~~~~~~
821
822Testing the node visitors can be complex, so extend your test cases from
823``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
824`tests/Twig/Node`_ directory.
825
826.. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register
827.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
828.. _`tests/Twig/Fixtures`: https://github.com/fabpot/Twig/tree/master/test/Twig/Tests/Fixtures
829.. _`tests/Twig/Node`: https://github.com/fabpot/Twig/tree/master/test/Twig/Tests/Node