]>
Commit | Line | Data |
---|---|---|
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 |