]> git.immae.eu Git - github/wallabag/wallabag.git/blame - vendor/twig/twig/doc/advanced_legacy.rst
gitignore vendor
[github/wallabag/wallabag.git] / vendor / twig / twig / doc / advanced_legacy.rst
CommitLineData
4f5b44bd
NL
1Extending 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
10Twig can be extended in many ways; you can add extra tags, filters, tests,
11operators, global variables, and functions. You can even extend the parser
12itself with node visitors.
13
14.. note::
15
16 The first section of this chapter describes how to extend Twig easily. If
17 you want to reuse your changes in different projects or if you want to
18 share them with others, you should then create an extension as described
19 in the following section.
20
21.. caution::
22
23 When extending Twig 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
29Before extending Twig, you must understand the differences between all the
30different possible extension points and when to use them.
31
32First, 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
38To understand why Twig exposes so many extension points, let's see how to
39implement a *Lorem ipsum* generator (it needs to know the number of words to
40generate).
41
42You can use a ``lipsum`` *tag*:
43
44.. code-block:: jinja
45
46 {% lipsum 40 %}
47
48That works, but using a tag for ``lipsum`` is not a good idea for at least
49three 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
59In fact, you rarely need to create tags; and that's good news because tags are
60the most complex extension point of Twig.
61
62Now, let's use a ``lipsum`` *filter*:
63
64.. code-block:: jinja
65
66 {{ 40|lipsum }}
67
68Again, it works, but it looks weird. A filter transforms the passed value to
69something else but here we use the value to indicate the number of words to
70generate (so, ``40`` is an argument of the filter, not the value we want to
71transform).
72
73Next, let's use a ``lipsum`` *function*:
74
75.. code-block:: jinja
76
77 {{ lipsum(40) }}
78
79Here we go. For this specific example, the creation of a function is the
80extension 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
88Last but not the least, you can also use a *global* object with a method able
89to generate lorem ipsum text:
90
91.. code-block:: jinja
92
93 {{ text.lipsum(40) }}
94
95As a rule of thumb, use functions for frequently used features and global
96objects for everything else.
97
98Keep in mind the following when you want to extend Twig:
99
100========== ========================== ========== =========================
101What? 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
112Globals
113-------
114
115A global variable is like any other template variable, except that it's
116available in all templates and macros::
117
118 $twig = new Twig_Environment($loader);
119 $twig->addGlobal('text', new Text());
120
121You can then use the ``text`` variable anywhere in a template:
122
123.. code-block:: jinja
124
125 {{ text.lipsum(40) }}
126
127Filters
128-------
129
130A filter is a regular PHP function or an object method that takes the left
131side of the filter (before the pipe ``|``) as first argument and the extra
132arguments passed to the filter (within parentheses ``()``) as extra arguments.
133
134Defining a filter is as easy as associating the filter name with a PHP
135callable. For instance, let's say you have the following code in a template:
136
137.. code-block:: jinja
138
139 {{ 'TWIG'|lower }}
140
141When compiling this template to PHP, Twig looks for the PHP callable
142associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig
143filter, and it is simply mapped to the PHP ``strtolower()`` function. After
144compilation, the generated PHP code is roughly equivalent to:
145
146.. code-block:: html+php
147
148 <?php echo strtolower('TWIG') ?>
149
150As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP
151function.
152
153A filter can also take extra arguments like in the following example:
154
155.. code-block:: jinja
156
157 {{ now|date('d/m/Y') }}
158
159In this case, the extra arguments are passed to the function after the main
160argument, 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
166Let's see how to create a new filter.
167
168In 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
170expected output:
171
172.. code-block:: jinja
173
174 {{ "Twig"|rot13 }}
175
176 {# should displays Gjvt #}
177
178Adding 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
184The second argument of ``addFilter()`` is an instance of ``Twig_Filter``.
185Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The
186first argument passed to the ``Twig_Filter_Function`` constructor is the name
187of the PHP function to call, here ``str_rot13``, a native PHP function.
188
189Let'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
197As the PHP ``str_rot13()`` function does not support this requirement, let's
198create a new PHP function::
199
200 function project_compute_rot13($string, $prefix = '')
201 {
202 return $prefix.str_rot13($string);
203 }
204
205As you can see, the ``prefix`` argument of the filter is passed as an extra
206argument to the ``project_compute_rot13()`` function.
207
208Adding this filter is as easy as before::
209
210 $twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13'));
211
212For better encapsulation, a filter can also be defined as a static method of a
213class. The ``Twig_Filter_Function`` class can also be used to register such
214static 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
223Environment aware Filters
224~~~~~~~~~~~~~~~~~~~~~~~~~
225
226The ``Twig_Filter`` classes take options as their last argument. For instance,
227if 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
232Twig will then pass the current environment as the first argument to the
233filter 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
243Automatic Escaping
244~~~~~~~~~~~~~~~~~~
245
246If automatic escaping is enabled, the output of the filter may be escaped
247before printing. If your filter acts as an escaper (or explicitly outputs html
248or javascript code), you will want the raw output to be printed. In such a
249case, set the ``is_safe`` option::
250
251 $filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
252
253Some filters may need to work on input that is already escaped or safe, for
254example when adding (safe) html tags to originally unsafe output. In such a
255case, set the ``pre_escape`` option to escape the input data before it is run
256through your filter::
257
258 $filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
259
260Dynamic Filters
261~~~~~~~~~~~~~~~
262
263.. versionadded:: 1.5
264 Dynamic filters support was added in Twig 1.5.
265
266A filter name containing the special ``*`` character is a dynamic filter as
267the ``*`` 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
276The following filters will be matched by the above defined dynamic filter:
277
278* ``product_path``
279* ``category_path``
280
281A 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
290The filter will receive all dynamic part values before the normal filters
291arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the
292following PHP call: ``twig_path('a', 'b', 'foo')``.
293
294Functions
295---------
296
297A function is a regular PHP function or an object method that can be called from
298templates.
299
300.. code-block:: jinja
301
302 {{ constant("DATE_W3C") }}
303
304When compiling this template to PHP, Twig looks for the PHP callable
305associated with the ``constant`` function. The ``constant`` function is a built-in Twig
306function, and it is simply mapped to the PHP ``constant()`` function. After
307compilation, the generated PHP code is roughly equivalent to:
308
309.. code-block:: html+php
310
311 <?php echo constant('DATE_W3C') ?>
312
313Adding 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
319You 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
325Functions also support ``needs_environment`` and ``is_safe`` parameters.
326
327Dynamic Functions
328~~~~~~~~~~~~~~~~~
329
330.. versionadded:: 1.5
331 Dynamic functions support was added in Twig 1.5.
332
333A function name containing the special ``*`` character is a dynamic function
334as 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
343The following functions will be matched by the above defined dynamic function:
344
345* ``product_path``
346* ``category_path``
347
348A 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
357The function will receive all dynamic part values before the normal functions
358arguments. For instance, a call to ``a_path_b('foo')`` will result in the
359following PHP call: ``twig_path('a', 'b', 'foo')``.
360
361Tags
362----
363
364One of the most exciting feature of a template engine like Twig is the
365possibility to define new language constructs. This is also the most complex
366feature as you need to understand how Twig's internals work.
367
368Let's create a simple ``set`` tag that allows the definition of simple
369variables 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
386Three 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
394Registering a new tag
395~~~~~~~~~~~~~~~~~~~~~
396
397Adding 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
403Defining a Token Parser
404~~~~~~~~~~~~~~~~~~~~~~~
405
406Now, 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
428The ``getTag()`` method must return the tag we want to parse, here ``set``.
429
430The ``parse()`` method is invoked whenever the parser encounters a ``set``
431tag. 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
434The parsing process is simplified thanks to a bunch of methods you can call
435from 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
451Parsing expressions is done by calling the ``parseExpression()`` like we did for
452the ``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
459Defining a Node
460~~~~~~~~~~~~~~~
461
462The ``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
482The compiler implements a fluid interface and provides methods that helps the
483developer 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
508Creating an Extension
509---------------------
510
511The main motivation for writing an extension is to move often used code into a
512reusable class like adding support for internationalization. An extension can
513define tags, filters, tests, operators, global variables, functions, and node
514visitors.
515
516Creating an extension also makes for a better separation of code that is
517executed at compilation time and code needed at runtime. As such, it makes
518your code faster.
519
520Most of the time, it is useful to create a single extension for your project,
521to 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
534An 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
604To 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
606way, you just need to implement the ``getName()`` method as the
607``Twig_Extension`` provides empty implementations for all other methods.
608
609The ``getName()`` method must return a unique identifier for your extension.
610
611Now, with this information in mind, let's create the most basic extension
612possible::
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
627Twig does not care where you save your extension on the filesystem, as all
628extensions must be registered explicitly to be available in your templates.
629
630You can register an extension by using the ``addExtension()`` method on your
631main ``Environment`` object::
632
633 $twig = new Twig_Environment($loader);
634 $twig->addExtension(new Project_Twig_Extension());
635
636Of 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
643Globals
644~~~~~~~
645
646Global variables can be registered in an extension via the ``getGlobals()``
647method::
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
661Functions
662~~~~~~~~~
663
664Functions can be registered in an extension via the ``getFunctions()``
665method::
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
679Filters
680~~~~~~~
681
682To add a filter to an extension, you need to override the ``getFilters()``
683method. This method must return an array of filters to add to the Twig
684environment::
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
698As you can see in the above code, the ``getFilters()`` method returns an array
699where keys are the name of the filters (``rot13``) and the values the
700definition of the filter (``new Twig_Filter_Function('str_rot13')``).
701
702As seen in the previous chapter, you can also define filters as static methods
703on the extension class::
704
705$twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'));
706
707You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function``
708when 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
727The first argument of the ``Twig_Filter_Method`` constructor is always
728``$this``, the current extension object. The second one is the name of the
729method to call.
730
731Using methods for filters is a great way to package your filter without
732polluting the global namespace. This also gives the developer more flexibility
733at the cost of a small overhead.
734
735Overriding default Filters
736..........................
737
738If some default core filters do not suit your needs, you can easily override
739them by creating your own extension. Just use the same names as the one you
740want 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
763Here, we override the ``date`` filter with a custom one. Using this extension
764is 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
770Tags
771~~~~
772
773Adding a tag in an extension can be done by overriding the
774``getTokenParsers()`` method. This method must return an array of tags to add
775to 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
787In the above code, we have added a single new tag, defined by the
788``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
789responsible for parsing the tag and compiling it to PHP.
790
791Operators
792~~~~~~~~~
793
794The ``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
815Tests
816~~~~~
817
818The ``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
832Testing an Extension
833--------------------
834
835.. versionadded:: 1.10
836 Support for functional tests was added in Twig 1.10.
837
838Functional Tests
839~~~~~~~~~~~~~~~~
840
841You can create functional tests for extensions simply by creating the
842following 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
856The ``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
874Fixtures examples can be found within the Twig repository
875`tests/Twig/Fixtures`_ directory.
876
877Node Tests
878~~~~~~~~~~
879
880Testing 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