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