]> git.immae.eu Git - github/wallabag/wallabag.git/blob - vendor/twig/twig/doc/advanced.rst
e1945ebb38a5109717afa3f541b014cca87a0d89
[github/wallabag/wallabag.git] / vendor / twig / twig / doc / advanced.rst
1 Extending 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
10 Twig can be extended in many ways; you can add extra tags, filters, tests,
11 operators, global variables, and functions. You can even extend the parser
12 itself 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
28 Before extending Twig, you must understand the differences between all the
29 different possible extension points and when to use them.
30
31 First, 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
37 To understand why Twig exposes so many extension points, let's see how to
38 implement a *Lorem ipsum* generator (it needs to know the number of words to
39 generate).
40
41 You can use a ``lipsum`` *tag*:
42
43 .. code-block:: jinja
44
45 {% lipsum 40 %}
46
47 That works, but using a tag for ``lipsum`` is not a good idea for at least
48 three 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
58 In fact, you rarely need to create tags; and that's good news because tags are
59 the most complex extension point of Twig.
60
61 Now, let's use a ``lipsum`` *filter*:
62
63 .. code-block:: jinja
64
65 {{ 40|lipsum }}
66
67 Again, it works, but it looks weird. A filter transforms the passed value to
68 something else but here we use the value to indicate the number of words to
69 generate (so, ``40`` is an argument of the filter, not the value we want to
70 transform).
71
72 Next, let's use a ``lipsum`` *function*:
73
74 .. code-block:: jinja
75
76 {{ lipsum(40) }}
77
78 Here we go. For this specific example, the creation of a function is the
79 extension 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
87 Last but not the least, you can also use a *global* object with a method able
88 to generate lorem ipsum text:
89
90 .. code-block:: jinja
91
92 {{ text.lipsum(40) }}
93
94 As a rule of thumb, use functions for frequently used features and global
95 objects for everything else.
96
97 Keep in mind the following when you want to extend Twig:
98
99 ========== ========================== ========== =========================
100 What? 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
111 Globals
112 -------
113
114 A global variable is like any other template variable, except that it's
115 available in all templates and macros::
116
117 $twig = new Twig_Environment($loader);
118 $twig->addGlobal('text', new Text());
119
120 You can then use the ``text`` variable anywhere in a template:
121
122 .. code-block:: jinja
123
124 {{ text.lipsum(40) }}
125
126 Filters
127 -------
128
129 Creating 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
142 The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
143 of the filter you will use in templates and the second one is the PHP callable
144 to associate with it.
145
146 Then, add the filter to your Twig environment::
147
148 $twig = new Twig_Environment($loader);
149 $twig->addFilter($filter);
150
151 And here is how to use it in a template:
152
153 .. code-block:: jinja
154
155 {{ 'Twig'|rot13 }}
156
157 {# will output Gjvt #}
158
159 When 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
161 to the filter (within parentheses ``()``) as extra arguments.
162
163 For instance, the following code:
164
165 .. code-block:: jinja
166
167 {{ 'TWIG'|lower }}
168 {{ now|date('d/m/Y') }}
169
170 is compiled to something like the following::
171
172 <?php echo strtolower('TWIG') ?>
173 <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
174
175 The ``Twig_SimpleFilter`` class takes an array of options as its last
176 argument::
177
178 $filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
179
180 Environment aware Filters
181 ~~~~~~~~~~~~~~~~~~~~~~~~~
182
183 If you want to access the current environment instance in your filter, set the
184 ``needs_environment`` option to ``true``; Twig will pass the current
185 environment 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
194 Context aware Filters
195 ~~~~~~~~~~~~~~~~~~~~~
196
197 If 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
199 the 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
210 Automatic Escaping
211 ~~~~~~~~~~~~~~~~~~
212
213 If automatic escaping is enabled, the output of the filter may be escaped
214 before printing. If your filter acts as an escaper (or explicitly outputs html
215 or JavaScript code), you will want the raw output to be printed. In such a
216 case, set the ``is_safe`` option::
217
218 $filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
219
220 Some filters may need to work on input that is already escaped or safe, for
221 example when adding (safe) html tags to originally unsafe output. In such a
222 case, set the ``pre_escape`` option to escape the input data before it is run
223 through your filter::
224
225 $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
226
227 Dynamic Filters
228 ~~~~~~~~~~~~~~~
229
230 A filter name containing the special ``*`` character is a dynamic filter as
231 the ``*`` can be any string::
232
233 $filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) {
234 // ...
235 });
236
237 The following filters will be matched by the above defined dynamic filter:
238
239 * ``product_path``
240 * ``category_path``
241
242 A dynamic filter can define more than one dynamic parts::
243
244 $filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {
245 // ...
246 });
247
248 The filter will receive all dynamic part values before the normal filter
249 arguments, 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
251 the filter: ``('a', 'b', 'foo')``.
252
253 Functions
254 ---------
255
256 Functions are defined in the exact same way as filters, but you need to create
257 an 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
265 Functions support the same features as filters, except for the ``pre_escape``
266 and ``preserves_safety`` options.
267
268 Tests
269 -----
270
271 Tests are defined in the exact same way as filters and functions, but you need
272 to 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
280 Tests allow you to create custom application specific logic for evaluating
281 boolean conditions. As a simple, example let's create a Twig test that checks if
282 objects 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
296 Test functions should always return true/false.
297
298 When creating tests you can use the ``node_class`` option to provide custom test
299 compilation. This is useful if your test can be compiled into PHP primitives.
300 This 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
322 The above example, shows how you can create tests that use a node class. The
323 node class has access to one sub-node called 'node'. This sub-node contains the
324 value 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
330 The ``node`` sub-node will contain an expression of ``my_value``. Node based
331 tests also have access to the ``arguments`` node. This node will contain the
332 various other arguments that have been provided to your test.
333
334 Tags
335 ----
336
337 One of the most exciting feature of a template engine like Twig is the
338 possibility to define new language constructs. This is also the most complex
339 feature as you need to understand how Twig's internals work.
340
341 Let's create a simple ``set`` tag that allows the definition of simple
342 variables 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
359 Three 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
367 Registering a new tag
368 ~~~~~~~~~~~~~~~~~~~~~
369
370 Adding 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
376 Defining a Token Parser
377 ~~~~~~~~~~~~~~~~~~~~~~~
378
379 Now, 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
402 The ``getTag()`` method must return the tag we want to parse, here ``set``.
403
404 The ``parse()`` method is invoked whenever the parser encounters a ``set``
405 tag. 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
408 The parsing process is simplified thanks to a bunch of methods you can call
409 from 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
425 Parsing expressions is done by calling the ``parseExpression()`` like we did for
426 the ``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
433 Defining a Node
434 ~~~~~~~~~~~~~~~
435
436 The ``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
456 The compiler implements a fluid interface and provides methods that helps the
457 developer 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
482 Creating an Extension
483 ---------------------
484
485 The main motivation for writing an extension is to move often used code into a
486 reusable class like adding support for internationalization. An extension can
487 define tags, filters, tests, operators, global variables, functions, and node
488 visitors.
489
490 Creating an extension also makes for a better separation of code that is
491 executed at compilation time and code needed at runtime. As such, it makes
492 your code faster.
493
494 Most of the time, it is useful to create a single extension for your project,
495 to 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
508 An 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
578 To 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
580 way, you just need to implement the ``getName()`` method as the
581 ``Twig_Extension`` provides empty implementations for all other methods.
582
583 The ``getName()`` method must return a unique identifier for your extension.
584
585 Now, with this information in mind, let's create the most basic extension
586 possible::
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
601 Twig does not care where you save your extension on the filesystem, as all
602 extensions must be registered explicitly to be available in your templates.
603
604 You can register an extension by using the ``addExtension()`` method on your
605 main ``Environment`` object::
606
607 $twig = new Twig_Environment($loader);
608 $twig->addExtension(new Project_Twig_Extension());
609
610 Of 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
617 Globals
618 ~~~~~~~
619
620 Global variables can be registered in an extension via the ``getGlobals()``
621 method::
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
635 Functions
636 ~~~~~~~~~
637
638 Functions can be registered in an extension via the ``getFunctions()``
639 method::
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
653 Filters
654 ~~~~~~~
655
656 To add a filter to an extension, you need to override the ``getFilters()``
657 method. This method must return an array of filters to add to the Twig
658 environment::
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
672 Tags
673 ~~~~
674
675 Adding a tag in an extension can be done by overriding the
676 ``getTokenParsers()`` method. This method must return an array of tags to add
677 to 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
689 In the above code, we have added a single new tag, defined by the
690 ``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
691 responsible for parsing the tag and compiling it to PHP.
692
693 Operators
694 ~~~~~~~~~
695
696 The ``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
717 Tests
718 ~~~~~
719
720 The ``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
734 Overloading
735 -----------
736
737 To overload an already defined filter, test, operator, global variable, or
738 function, 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
745 Here, we have overloaded the built-in ``date`` filter with a custom one.
746
747 That 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
777 Testing an Extension
778 --------------------
779
780 Functional Tests
781 ~~~~~~~~~~~~~~~~
782
783 You can create functional tests for extensions simply by creating the
784 following 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
798 The ``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
816 Fixtures examples can be found within the Twig repository
817 `tests/Twig/Fixtures`_ directory.
818
819 Node Tests
820 ~~~~~~~~~~
821
822 Testing 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