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