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', ['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', ['is_safe' => ['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', ['pre_escape' => 'html', 'is_safe' => ['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\Extension\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\AbstractTokenParser
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\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\Node
465    {
466        public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $lineno, $tag = null)
467        {
468            parent::__construct(['value' => $value], ['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\ForNode`` 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\BlockNode`` for a
501  usage example).
502
503* ``outdent()``: Outdents the generated code (see ``\Twig\Node\BlockNode`` 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: https://github.com/twigphp/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        function initRuntime(\Twig\Environment $environment);
544
545        /**
546         * Returns the token parser instances to add to the existing list.
547         *
548         * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[]
549         */
550        function getTokenParsers();
551
552        /**
553         * Returns the node visitor instances to add to the existing list.
554         *
555         * @return \Twig\NodeVisitor\NodeVisitorInterface[]
556         */
557        function getNodeVisitors();
558
559        /**
560         * Returns a list of filters to add to the existing list.
561         *
562         * @return \Twig\TwigFilter[]
563         */
564        function getFilters();
565
566        /**
567         * Returns a list of tests to add to the existing list.
568         *
569         * @return \Twig\TwigTest[]
570         */
571        function getTests();
572
573        /**
574         * Returns a list of functions to add to the existing list.
575         *
576         * @return \Twig\TwigFunction[]
577         */
578        function getFunctions();
579
580        /**
581         * Returns a list of operators to add to the existing list.
582         *
583         * @return array<array> First array of unary operators, second array of binary operators
584         */
585        function getOperators();
586
587        /**
588         * Returns a list of global variables to add to the existing list.
589         *
590         * @return array An array of global variables
591         */
592        function getGlobals();
593
594        /**
595         * Returns the name of the extension.
596         *
597         * @return string The extension name
598         */
599        function getName();
600    }
601
602To keep your extension class clean and lean, it can inherit from the built-in
603``\Twig\Extension\AbstractExtension`` class instead of implementing the whole interface. That
604way, you just need to implement the ``getName()`` method as the
605``\Twig\Extension\AbstractExtension`` provides empty implementations for all other methods.
606
607The ``getName()`` method must return a unique identifier for your extension.
608
609Now, with this information in mind, let's create the most basic extension
610possible::
611
612    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
613    {
614        public function getName()
615        {
616            return 'project';
617        }
618    }
619
620.. note::
621
622    Of course, this extension does nothing for now. We will customize it in
623    the next sections.
624
625Twig does not care where you save your extension on the filesystem, as all
626extensions must be registered explicitly to be available in your templates.
627
628You can register an extension by using the ``addExtension()`` method on your
629main ``Environment`` object::
630
631    $twig = new \Twig\Environment($loader);
632    $twig->addExtension(new Project_Twig_Extension());
633
634Of course, you need to first load the extension file by either using
635``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
636
637.. tip::
638
639    The bundled extensions are great examples of how extensions work.
640
641Globals
642~~~~~~~
643
644Global variables can be registered in an extension via the ``getGlobals()``
645method::
646
647    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
648    {
649        public function getGlobals()
650        {
651            return [
652                'text' => new Text(),
653            ];
654        }
655
656        // ...
657    }
658
659Functions
660~~~~~~~~~
661
662Functions can be registered in an extension via the ``getFunctions()``
663method::
664
665    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
666    {
667        public function getFunctions()
668        {
669            return [
670                'lipsum' => new Twig_Function_Function('generate_lipsum'),
671            ];
672        }
673
674        // ...
675    }
676
677Filters
678~~~~~~~
679
680To add a filter to an extension, you need to override the ``getFilters()``
681method. This method must return an array of filters to add to the Twig
682environment::
683
684    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
685    {
686        public function getFilters()
687        {
688            return [
689                'rot13' => new Twig_Filter_Function('str_rot13'),
690            ];
691        }
692
693        // ...
694    }
695
696As you can see in the above code, the ``getFilters()`` method returns an array
697where keys are the name of the filters (``rot13``) and the values the
698definition of the filter (``new Twig_Filter_Function('str_rot13')``).
699
700As seen in the previous chapter, you can also define filters as static methods
701on the extension class::
702
703$twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'));
704
705You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function``
706when defining a filter to use a method::
707
708    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
709    {
710        public function getFilters()
711        {
712            return [
713                'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
714            ];
715        }
716
717        public function rot13Filter($string)
718        {
719            return str_rot13($string);
720        }
721
722        // ...
723    }
724
725The first argument of the ``Twig_Filter_Method`` constructor is always
726``$this``, the current extension object. The second one is the name of the
727method to call.
728
729Using methods for filters is a great way to package your filter without
730polluting the global namespace. This also gives the developer more flexibility
731at the cost of a small overhead.
732
733Overriding default Filters
734..........................
735
736If some default core filters do not suit your needs, you can easily override
737them by creating your own extension. Just use the same names as the one you
738want to override::
739
740    class MyCoreExtension extends \Twig\Extension\AbstractExtension
741    {
742        public function getFilters()
743        {
744            return [
745                'date' => new Twig_Filter_Method($this, 'dateFilter'),
746                // ...
747            ];
748        }
749
750        public function dateFilter($timestamp, $format = 'F j, Y H:i')
751        {
752            return '...'.twig_date_format_filter($timestamp, $format);
753        }
754
755        public function getName()
756        {
757            return 'project';
758        }
759    }
760
761Here, we override the ``date`` filter with a custom one. Using this extension
762is as simple as registering the ``MyCoreExtension`` extension by calling the
763``addExtension()`` method on the environment instance::
764
765    $twig = new \Twig\Environment($loader);
766    $twig->addExtension(new MyCoreExtension());
767
768Tags
769~~~~
770
771Adding a tag in an extension can be done by overriding the
772``getTokenParsers()`` method. This method must return an array of tags to add
773to the Twig environment::
774
775    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
776    {
777        public function getTokenParsers()
778        {
779            return [new Project_Set_TokenParser()];
780        }
781
782        // ...
783    }
784
785In the above code, we have added a single new tag, defined by the
786``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
787responsible for parsing the tag and compiling it to PHP.
788
789Operators
790~~~~~~~~~
791
792The ``getOperators()`` methods allows to add new operators. Here is how to add
793``!``, ``||``, and ``&&`` operators::
794
795    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
796    {
797        public function getOperators()
798        {
799            return [
800                [
801                    '!' => ['precedence' => 50, 'class' => '\Twig\Node\Expression\Unary\NotUnary'],
802                ),
803                [
804                    '||' => ['precedence' => 10, 'class' => '\Twig\Node\Expression\Binary\OrBinary', 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
805                    '&&' => ['precedence' => 15, 'class' => '\Twig\Node\Expression\Binary\AndBinary', 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
806                ],
807            ];
808        }
809
810        // ...
811    }
812
813Tests
814~~~~~
815
816The ``getTests()`` methods allows to add new test functions::
817
818    class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
819    {
820        public function getTests()
821        {
822            return [
823                'even' => new Twig_Test_Function('twig_test_even'),
824            ];
825        }
826
827        // ...
828    }
829
830Testing an Extension
831--------------------
832
833.. versionadded:: 1.10
834    Support for functional tests was added in Twig 1.10.
835
836Functional Tests
837~~~~~~~~~~~~~~~~
838
839You can create functional tests for extensions simply by creating the
840following file structure in your test directory::
841
842    Fixtures/
843        filters/
844            foo.test
845            bar.test
846        functions/
847            foo.test
848            bar.test
849        tags/
850            foo.test
851            bar.test
852    IntegrationTest.php
853
854The ``IntegrationTest.php`` file should look like this::
855
856    class Project_Tests_IntegrationTest extends \Twig\Test\IntegrationTestCase
857    {
858        public function getExtensions()
859        {
860            return [
861                new Project_Twig_Extension1(),
862                new Project_Twig_Extension2(),
863            ];
864        }
865
866        public function getFixturesDir()
867        {
868            return dirname(__FILE__).'/Fixtures/';
869        }
870    }
871
872Fixtures examples can be found within the Twig repository
873`tests/Twig/Fixtures`_ directory.
874
875Node Tests
876~~~~~~~~~~
877
878Testing the node visitors can be complex, so extend your test cases from
879``\Twig\Test\NodeTestCase``. Examples can be found in the Twig repository
880`tests/Twig/Node`_ directory.
881
882.. _`spl_autoload_register()`: https://secure.php.net/spl_autoload_register
883.. _`rot13`:                   https://secure.php.net/manual/en/function.str-rot13.php
884.. _`tests/Twig/Fixtures`:     https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures
885.. _`tests/Twig/Node`:         https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node
886