1Extending Twig 2============== 3 4.. caution:: 5 6 This section describes how to extend Twig as of **Twig 1.12**. If you are 7 using an older version, read the :doc:`legacy<advanced_legacy>` chapter 8 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 without creating an extension, Twig won't be able to 24 recompile your templates when the PHP code is updated. To see your changes 25 in real-time, either disable template caching or package your code into an 26 extension (see the next section of this chapter). 27 28Before extending Twig, you must understand the differences between all the 29different possible extension points and when to use them. 30 31First, remember that Twig has two main language constructs: 32 33* ``{{ }}``: used to print the result of an expression evaluation; 34 35* ``{% %}``: used to execute statements. 36 37To understand why Twig exposes so many extension points, let's see how to 38implement a *Lorem ipsum* generator (it needs to know the number of words to 39generate). 40 41You can use a ``lipsum`` *tag*: 42 43.. code-block:: jinja 44 45 {% lipsum 40 %} 46 47That works, but using a tag for ``lipsum`` is not a good idea for at least 48three main reasons: 49 50* ``lipsum`` is not a language construct; 51* The tag outputs something; 52* The tag is not flexible as you cannot use it in an expression: 53 54 .. code-block:: jinja 55 56 {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }} 57 58In fact, you rarely need to create tags; and that's good news because tags are 59the most complex extension point of Twig. 60 61Now, let's use a ``lipsum`` *filter*: 62 63.. code-block:: jinja 64 65 {{ 40|lipsum }} 66 67Again, it works, but it looks weird. A filter transforms the passed value to 68something else but here we use the value to indicate the number of words to 69generate (so, ``40`` is an argument of the filter, not the value we want to 70transform). 71 72Next, let's use a ``lipsum`` *function*: 73 74.. code-block:: jinja 75 76 {{ lipsum(40) }} 77 78Here we go. For this specific example, the creation of a function is the 79extension point to use. And you can use it anywhere an expression is accepted: 80 81.. code-block:: jinja 82 83 {{ 'some text' ~ lipsum(40) ~ 'some more text' }} 84 85 {% set lipsum = lipsum(40) %} 86 87Last but not the least, you can also use a *global* object with a method able 88to generate lorem ipsum text: 89 90.. code-block:: jinja 91 92 {{ text.lipsum(40) }} 93 94As a rule of thumb, use functions for frequently used features and global 95objects for everything else. 96 97Keep in mind the following when you want to extend Twig: 98 99========== ========================== ========== ========================= 100What? Implementation difficulty? How often? When? 101========== ========================== ========== ========================= 102*macro* trivial frequent Content generation 103*global* trivial frequent Helper object 104*function* trivial frequent Content generation 105*filter* trivial frequent Value transformation 106*tag* complex rare DSL language construct 107*test* trivial rare Boolean decision 108*operator* trivial rare Values transformation 109========== ========================== ========== ========================= 110 111Globals 112------- 113 114A global variable is like any other template variable, except that it's 115available in all templates and macros:: 116 117 $twig = new \Twig\Environment($loader); 118 $twig->addGlobal('text', new Text()); 119 120You can then use the ``text`` variable anywhere in a template: 121 122.. code-block:: jinja 123 124 {{ text.lipsum(40) }} 125 126Filters 127------- 128 129Creating a filter is as simple as associating a name with a PHP callable:: 130 131 // an anonymous function 132 $filter = new \Twig\TwigFilter('rot13', function ($string) { 133 return str_rot13($string); 134 }); 135 136 // or a simple PHP function 137 $filter = new \Twig\TwigFilter('rot13', 'str_rot13'); 138 139 // or a class static method 140 $filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']); 141 $filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter'); 142 143 // or a class method 144 $filter = new \Twig\TwigFilter('rot13', [$this, 'rot13Filter']); 145 // the one below needs a runtime implementation (see below for more information) 146 $filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']); 147 148The first argument passed to the ``\Twig\TwigFilter`` constructor is the name 149of the filter you will use in templates and the second one is the PHP callable 150to associate with it. 151 152Then, add the filter to your Twig environment:: 153 154 $twig = new \Twig\Environment($loader); 155 $twig->addFilter($filter); 156 157And here is how to use it in a template: 158 159.. code-block:: jinja 160 161 {{ 'Twig'|rot13 }} 162 163 {# will output Gjvt #} 164 165When called by Twig, the PHP callable receives the left side of the filter 166(before the pipe ``|``) as the first argument and the extra arguments passed 167to the filter (within parentheses ``()``) as extra arguments. 168 169For instance, the following code: 170 171.. code-block:: jinja 172 173 {{ 'TWIG'|lower }} 174 {{ now|date('d/m/Y') }} 175 176is compiled to something like the following:: 177 178 <?php echo strtolower('TWIG') ?> 179 <?php echo twig_date_format_filter($now, 'd/m/Y') ?> 180 181The ``\Twig\TwigFilter`` class takes an array of options as its last 182argument:: 183 184 $filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options); 185 186Environment-aware Filters 187~~~~~~~~~~~~~~~~~~~~~~~~~ 188 189If you want to access the current environment instance in your filter, set the 190``needs_environment`` option to ``true``; Twig will pass the current 191environment as the first argument to the filter call:: 192 193 $filter = new \Twig\TwigFilter('rot13', function (Twig_Environment $env, $string) { 194 // get the current charset for instance 195 $charset = $env->getCharset(); 196 197 return str_rot13($string); 198 }, ['needs_environment' => true]); 199 200Context-aware Filters 201~~~~~~~~~~~~~~~~~~~~~ 202 203If you want to access the current context in your filter, set the 204``needs_context`` option to ``true``; Twig will pass the current context as 205the first argument to the filter call (or the second one if 206``needs_environment`` is also set to ``true``):: 207 208 $filter = new \Twig\TwigFilter('rot13', function ($context, $string) { 209 // ... 210 }, ['needs_context' => true]); 211 212 $filter = new \Twig\TwigFilter('rot13', function (Twig_Environment $env, $context, $string) { 213 // ... 214 }, ['needs_context' => true, 'needs_environment' => true]); 215 216Automatic Escaping 217~~~~~~~~~~~~~~~~~~ 218 219If automatic escaping is enabled, the output of the filter may be escaped 220before printing. If your filter acts as an escaper (or explicitly outputs HTML 221or JavaScript code), you will want the raw output to be printed. In such a 222case, set the ``is_safe`` option:: 223 224 $filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]); 225 226Some filters may need to work on input that is already escaped or safe, for 227example when adding (safe) HTML tags to originally unsafe output. In such a 228case, set the ``pre_escape`` option to escape the input data before it is run 229through your filter:: 230 231 $filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]); 232 233Variadic Filters 234~~~~~~~~~~~~~~~~ 235 236.. versionadded:: 1.19 237 Support for variadic filters was added in Twig 1.19. 238 239When a filter should accept an arbitrary number of arguments, set the 240``is_variadic`` option to ``true``; Twig will pass the extra arguments as the 241last argument to the filter call as an array:: 242 243 $filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) { 244 // ... 245 }, ['is_variadic' => true]); 246 247Be warned that :ref:`named arguments <named-arguments>` passed to a variadic 248filter cannot be checked for validity as they will automatically end up in the 249option array. 250 251Dynamic Filters 252~~~~~~~~~~~~~~~ 253 254A filter name containing the special ``*`` character is a dynamic filter as 255the ``*`` can be any string:: 256 257 $filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) { 258 // ... 259 }); 260 261The following filters will be matched by the above defined dynamic filter: 262 263* ``product_path`` 264* ``category_path`` 265 266A dynamic filter can define more than one dynamic parts:: 267 268 $filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) { 269 // ... 270 }); 271 272The filter will receive all dynamic part values before the normal filter 273arguments, but after the environment and the context. For instance, a call to 274``'foo'|a_path_b()`` will result in the following arguments to be passed to 275the filter: ``('a', 'b', 'foo')``. 276 277Deprecated Filters 278~~~~~~~~~~~~~~~~~~ 279 280.. versionadded:: 1.21 281 Support for deprecated filters was added in Twig 1.21. 282 283You can mark a filter as being deprecated by setting the ``deprecated`` option 284to ``true``. You can also give an alternative filter that replaces the 285deprecated one when that makes sense:: 286 287 $filter = new \Twig\TwigFilter('obsolete', function () { 288 // ... 289 }, ['deprecated' => true, 'alternative' => 'new_one']); 290 291When a filter is deprecated, Twig emits a deprecation notice when compiling a 292template using it. See :ref:`deprecation-notices` for more information. 293 294Functions 295--------- 296 297Functions are defined in the exact same way as filters, but you need to create 298an instance of ``\Twig\TwigFunction``:: 299 300 $twig = new \Twig\Environment($loader); 301 $function = new \Twig\TwigFunction('function_name', function () { 302 // ... 303 }); 304 $twig->addFunction($function); 305 306Functions support the same features as filters, except for the ``pre_escape`` 307and ``preserves_safety`` options. 308 309Tests 310----- 311 312Tests are defined in the exact same way as filters and functions, but you need 313to create an instance of ``\Twig\TwigTest``:: 314 315 $twig = new \Twig\Environment($loader); 316 $test = new \Twig\TwigTest('test_name', function () { 317 // ... 318 }); 319 $twig->addTest($test); 320 321Tests allow you to create custom application specific logic for evaluating 322boolean conditions. As a simple example, let's create a Twig test that checks if 323objects are 'red':: 324 325 $twig = new \Twig\Environment($loader); 326 $test = new \Twig\TwigTest('red', function ($value) { 327 if (isset($value->color) && $value->color == 'red') { 328 return true; 329 } 330 if (isset($value->paint) && $value->paint == 'red') { 331 return true; 332 } 333 return false; 334 }); 335 $twig->addTest($test); 336 337Test functions should always return true/false. 338 339When creating tests you can use the ``node_class`` option to provide custom test 340compilation. This is useful if your test can be compiled into PHP primitives. 341This is used by many of the tests built into Twig:: 342 343 $twig = new \Twig\Environment($loader); 344 $test = new \Twig\TwigTest( 345 'odd', 346 null, 347 ['node_class' => '\Twig\Node\Expression\Test\OddTest']); 348 $twig->addTest($test); 349 350 class Twig_Node_Expression_Test_Odd extends \Twig\Node\Expression\TestExpression 351 { 352 public function compile(\Twig\Compiler $compiler) 353 { 354 $compiler 355 ->raw('(') 356 ->subcompile($this->getNode('node')) 357 ->raw(' % 2 == 1') 358 ->raw(')') 359 ; 360 } 361 } 362 363The above example shows how you can create tests that use a node class. The 364node class has access to one sub-node called 'node'. This sub-node contains the 365value that is being tested. When the ``odd`` filter is used in code such as: 366 367.. code-block:: jinja 368 369 {% if my_value is odd %} 370 371The ``node`` sub-node will contain an expression of ``my_value``. Node-based 372tests also have access to the ``arguments`` node. This node will contain the 373various other arguments that have been provided to your test. 374 375.. versionadded:: 1.36 376 Dynamic tests support was added in Twig 1.36. 377 378If you want to pass a variable number of positional or named arguments to the 379test, set the ``is_variadic`` option to ``true``. Tests support dynamic 380names (see dynamic filters and functions for the syntax). 381 382Tags 383---- 384 385One of the most exciting features of a template engine like Twig is the 386possibility to define new **language constructs**. This is also the most complex 387feature as you need to understand how Twig's internals work. 388 389Most of the time though, a tag is not needed: 390 391* If your tag generates some output, use a **function** instead. 392 393* If your tag modifies some content and returns it, use a **filter** instead. 394 395 For instance, if you want to create a tag that converts a Markdown formatted 396 text to HTML, create a ``markdown`` filter instead: 397 398 .. code-block:: jinja 399 400 {{ '**markdown** text'|markdown }} 401 402 If you want use this filter on large amounts of text, wrap it with the 403 :doc:`filter <tags/filter>` tag: 404 405 .. code-block:: jinja 406 407 {% filter markdown %} 408 Title 409 ===== 410 411 Much better than creating a tag as you can **compose** filters. 412 {% endfilter %} 413 414* If your tag does not output anything, but only exists because of a side 415 effect, create a **function** that returns nothing and call it via the 416 :doc:`filter <tags/do>` tag. 417 418 For instance, if you want to create a tag that logs text, create a ``log`` 419 function instead and call it via the :doc:`do <tags/do>` tag: 420 421 .. code-block:: jinja 422 423 {% do log('Log some things') %} 424 425If you still want to create a tag for a new language construct, great! 426 427Let's create a simple ``set`` tag that allows the definition of simple 428variables from within a template. The tag can be used like follows: 429 430.. code-block:: jinja 431 432 {% set name = "value" %} 433 434 {{ name }} 435 436 {# should output value #} 437 438.. note:: 439 440 The ``set`` tag is part of the Core extension and as such is always 441 available. The built-in version is slightly more powerful and supports 442 multiple assignments by default (cf. the template designers chapter for 443 more information). 444 445Three steps are needed to define a new tag: 446 447* Defining a Token Parser class (responsible for parsing the template code); 448 449* Defining a Node class (responsible for converting the parsed code to PHP); 450 451* Registering the tag. 452 453Registering a new tag 454~~~~~~~~~~~~~~~~~~~~~ 455 456Adding a tag is as simple as calling the ``addTokenParser`` method on the 457``\Twig\Environment`` instance:: 458 459 $twig = new \Twig\Environment($loader); 460 $twig->addTokenParser(new Project_Set_TokenParser()); 461 462Defining a Token Parser 463~~~~~~~~~~~~~~~~~~~~~~~ 464 465Now, let's see the actual code of this class:: 466 467 class Project_Set_TokenParser extends \Twig\TokenParser\AbstractTokenParser 468 { 469 public function parse(\Twig\Token $token) 470 { 471 $parser = $this->parser; 472 $stream = $parser->getStream(); 473 474 $name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue(); 475 $stream->expect(\Twig\Token::OPERATOR_TYPE, '='); 476 $value = $parser->getExpressionParser()->parseExpression(); 477 $stream->expect(\Twig\Token::BLOCK_END_TYPE); 478 479 return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag()); 480 } 481 482 public function getTag() 483 { 484 return 'set'; 485 } 486 } 487 488The ``getTag()`` method must return the tag we want to parse, here ``set``. 489 490The ``parse()`` method is invoked whenever the parser encounters a ``set`` 491tag. It should return a ``\Twig\Node\Node`` instance that represents the node (the 492``Project_Set_Node`` calls creating is explained in the next section). 493 494The parsing process is simplified thanks to a bunch of methods you can call 495from the token stream (``$this->parser->getStream()``): 496 497* ``getCurrent()``: Gets the current token in the stream. 498 499* ``next()``: Moves to the next token in the stream, *but returns the old one*. 500 501* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether 502 the current token is of a particular type or value (or both). The value may be an 503 array of several possible values. 504 505* ``expect($type[, $value[, $message]])``: If the current token isn't of the given 506 type/value a syntax error is thrown. Otherwise, if the type and value are correct, 507 the token is returned and the stream moves to the next token. 508 509* ``look()``: Looks at the next token without consuming it. 510 511Parsing expressions is done by calling the ``parseExpression()`` like we did for 512the ``set`` tag. 513 514.. tip:: 515 516 Reading the existing ``TokenParser`` classes is the best way to learn all 517 the nitty-gritty details of the parsing process. 518 519Defining a Node 520~~~~~~~~~~~~~~~ 521 522The ``Project_Set_Node`` class itself is rather simple:: 523 524 class Project_Set_Node extends \Twig\Node\Node 525 { 526 public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line, $tag = null) 527 { 528 parent::__construct(['value' => $value], ['name' => $name], $line, $tag); 529 } 530 531 public function compile(\Twig\Compiler $compiler) 532 { 533 $compiler 534 ->addDebugInfo($this) 535 ->write('$context[\''.$this->getAttribute('name').'\'] = ') 536 ->subcompile($this->getNode('value')) 537 ->raw(";\n") 538 ; 539 } 540 } 541 542The compiler implements a fluid interface and provides methods that helps the 543developer generate beautiful and readable PHP code: 544 545* ``subcompile()``: Compiles a node. 546 547* ``raw()``: Writes the given string as is. 548 549* ``write()``: Writes the given string by adding indentation at the beginning 550 of each line. 551 552* ``string()``: Writes a quoted string. 553 554* ``repr()``: Writes a PHP representation of a given value (see 555 ``\Twig\Node\ForNode`` for a usage example). 556 557* ``addDebugInfo()``: Adds the line of the original template file related to 558 the current node as a comment. 559 560* ``indent()``: Indents the generated code (see ``\Twig\Node\BlockNode`` for a 561 usage example). 562 563* ``outdent()``: Outdents the generated code (see ``\Twig\Node\BlockNode`` for a 564 usage example). 565 566.. _creating_extensions: 567 568Creating an Extension 569--------------------- 570 571The main motivation for writing an extension is to move often used code into a 572reusable class like adding support for internationalization. An extension can 573define tags, filters, tests, operators, global variables, functions, and node 574visitors. 575 576Most of the time, it is useful to create a single extension for your project, 577to host all the specific tags and filters you want to add to Twig. 578 579.. tip:: 580 581 When packaging your code into an extension, Twig is smart enough to 582 recompile your templates whenever you make a change to it (when 583 ``auto_reload`` is enabled). 584 585.. note:: 586 587 Before writing your own extensions, have a look at the Twig official 588 extension repository: https://github.com/twigphp/Twig-extensions. 589 590An extension is a class that implements the following interface:: 591 592 interface Twig_ExtensionInterface 593 { 594 /** 595 * Initializes the runtime environment. 596 * 597 * This is where you can load some file that contains filter functions for instance. 598 * 599 * @deprecated since 1.23 (to be removed in 2.0), implement \Twig\Extension\InitRuntimeInterface instead 600 */ 601 function initRuntime(\Twig\Environment $environment); 602 603 /** 604 * Returns the token parser instances to add to the existing list. 605 * 606 * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[] 607 */ 608 function getTokenParsers(); 609 610 /** 611 * Returns the node visitor instances to add to the existing list. 612 * 613 * @return \Twig\NodeVisitor\NodeVisitorInterface[] 614 */ 615 function getNodeVisitors(); 616 617 /** 618 * Returns a list of filters to add to the existing list. 619 * 620 * @return \Twig\TwigFilter[] 621 */ 622 function getFilters(); 623 624 /** 625 * Returns a list of tests to add to the existing list. 626 * 627 * @return \Twig\TwigTest[] 628 */ 629 function getTests(); 630 631 /** 632 * Returns a list of functions to add to the existing list. 633 * 634 * @return \Twig\TwigFunction[] 635 */ 636 function getFunctions(); 637 638 /** 639 * Returns a list of operators to add to the existing list. 640 * 641 * @return array<array> First array of unary operators, second array of binary operators 642 */ 643 function getOperators(); 644 645 /** 646 * Returns a list of global variables to add to the existing list. 647 * 648 * @return array An array of global variables 649 * 650 * @deprecated since 1.23 (to be removed in 2.0), implement \Twig\Extension\GlobalsInterface instead 651 */ 652 function getGlobals(); 653 654 /** 655 * Returns the name of the extension. 656 * 657 * @return string The extension name 658 * 659 * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally 660 */ 661 function getName(); 662 } 663 664To keep your extension class clean and lean, inherit from the built-in 665``\Twig\Extension\AbstractExtension`` class instead of implementing the interface as it provides 666empty implementations for all methods: 667 668 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 669 { 670 } 671 672Of course, this extension does nothing for now. We will customize it in the 673next sections. 674 675.. note:: 676 677 Prior to Twig 1.26, you must implement the ``getName()`` method which must 678 return a unique identifier for the extension. 679 680Twig does not care where you save your extension on the filesystem, as all 681extensions must be registered explicitly to be available in your templates. 682 683You can register an extension by using the ``addExtension()`` method on your 684main ``Environment`` object:: 685 686 $twig = new \Twig\Environment($loader); 687 $twig->addExtension(new Project_Twig_Extension()); 688 689.. tip:: 690 691 The Twig core extensions are great examples of how extensions work. 692 693Globals 694~~~~~~~ 695 696Global variables can be registered in an extension via the ``getGlobals()`` 697method:: 698 699 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface 700 { 701 public function getGlobals() 702 { 703 return [ 704 'text' => new Text(), 705 ]; 706 } 707 708 // ... 709 } 710 711Functions 712~~~~~~~~~ 713 714Functions can be registered in an extension via the ``getFunctions()`` 715method:: 716 717 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 718 { 719 public function getFunctions() 720 { 721 return [ 722 new \Twig\TwigFunction('lipsum', 'generate_lipsum'), 723 ]; 724 } 725 726 // ... 727 } 728 729Filters 730~~~~~~~ 731 732To add a filter to an extension, you need to override the ``getFilters()`` 733method. This method must return an array of filters to add to the Twig 734environment:: 735 736 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 737 { 738 public function getFilters() 739 { 740 return [ 741 new \Twig\TwigFilter('rot13', 'str_rot13'), 742 ]; 743 } 744 745 // ... 746 } 747 748Tags 749~~~~ 750 751Adding a tag in an extension can be done by overriding the 752``getTokenParsers()`` method. This method must return an array of tags to add 753to the Twig environment:: 754 755 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 756 { 757 public function getTokenParsers() 758 { 759 return [new Project_Set_TokenParser()]; 760 } 761 762 // ... 763 } 764 765In the above code, we have added a single new tag, defined by the 766``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is 767responsible for parsing the tag and compiling it to PHP. 768 769Operators 770~~~~~~~~~ 771 772The ``getOperators()`` methods lets you add new operators. Here is how to add 773``!``, ``||``, and ``&&`` operators:: 774 775 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 776 { 777 public function getOperators() 778 { 779 return [ 780 [ 781 '!' => ['precedence' => 50, 'class' => '\Twig\Node\Expression\Unary\NotUnary'], 782 ], 783 [ 784 '||' => ['precedence' => 10, 'class' => '\Twig\Node\Expression\Binary\OrBinary', 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT], 785 '&&' => ['precedence' => 15, 'class' => '\Twig\Node\Expression\Binary\AndBinary', 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT], 786 ], 787 ]; 788 } 789 790 // ... 791 } 792 793Tests 794~~~~~ 795 796The ``getTests()`` method lets you add new test functions:: 797 798 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 799 { 800 public function getTests() 801 { 802 return [ 803 new \Twig\TwigTest('even', 'twig_test_even'), 804 ]; 805 } 806 807 // ... 808 } 809 810Definition vs Runtime 811~~~~~~~~~~~~~~~~~~~~~ 812 813Twig filters, functions, and tests runtime implementations can be defined as 814any valid PHP callable: 815 816* **functions/static methods**: Simple to implement and fast (used by all Twig 817 core extensions); but it is hard for the runtime to depend on external 818 objects; 819 820* **closures**: Simple to implement; 821 822* **object methods**: More flexible and required if your runtime code depends 823 on external objects. 824 825The simplest way to use methods is to define them on the extension itself:: 826 827 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 828 { 829 private $rot13Provider; 830 831 public function __construct($rot13Provider) 832 { 833 $this->rot13Provider = $rot13Provider; 834 } 835 836 public function getFunctions() 837 { 838 return [ 839 new \Twig\TwigFunction('rot13', [$this, 'rot13']), 840 ]; 841 } 842 843 public function rot13($value) 844 { 845 return $this->rot13Provider->rot13($value); 846 } 847 } 848 849This is very convenient but not recommended as it makes template compilation 850depend on runtime dependencies even if they are not needed (think for instance 851as a dependency that connects to a database engine). 852 853As of Twig 1.26, you can easily decouple the extension definitions from their 854runtime implementations by registering a ``\Twig\RuntimeLoader\RuntimeLoaderInterface`` 855instance on the environment that knows how to instantiate such runtime classes 856(runtime classes must be autoload-able):: 857 858 class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface 859 { 860 public function load($class) 861 { 862 // implement the logic to create an instance of $class 863 // and inject its dependencies 864 // most of the time, it means using your dependency injection container 865 if ('Project_Twig_RuntimeExtension' === $class) { 866 return new $class(new Rot13Provider()); 867 } else { 868 // ... 869 } 870 } 871 } 872 873 $twig->addRuntimeLoader(new RuntimeLoader()); 874 875.. note:: 876 877 As of Twig 1.32, Twig comes with a PSR-11 compatible runtime loader 878 (``\Twig\RuntimeLoader\ContainerRuntimeLoader``) that works on PHP 5.3+. 879 880It is now possible to move the runtime logic to a new 881``Project_Twig_RuntimeExtension`` class and use it directly in the extension:: 882 883 class Project_Twig_RuntimeExtension 884 { 885 private $rot13Provider; 886 887 public function __construct($rot13Provider) 888 { 889 $this->rot13Provider = $rot13Provider; 890 } 891 892 public function rot13($value) 893 { 894 return $this->rot13Provider->rot13($value); 895 } 896 } 897 898 class Project_Twig_Extension extends \Twig\Extension\AbstractExtension 899 { 900 public function getFunctions() 901 { 902 return [ 903 new \Twig\TwigFunction('rot13', ['Project_Twig_RuntimeExtension', 'rot13']), 904 // or 905 new \Twig\TwigFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'), 906 ]; 907 } 908 } 909 910Overloading 911----------- 912 913To overload an already defined filter, test, operator, global variable, or 914function, re-define it in an extension and register it **as late as 915possible** (order matters):: 916 917 class MyCoreExtension extends \Twig\Extension\AbstractExtension 918 { 919 public function getFilters() 920 { 921 return [ 922 new \Twig\TwigFilter('date', [$this, 'dateFilter']), 923 ]; 924 } 925 926 public function dateFilter($timestamp, $format = 'F j, Y H:i') 927 { 928 // do something different from the built-in date filter 929 } 930 } 931 932 $twig = new \Twig\Environment($loader); 933 $twig->addExtension(new MyCoreExtension()); 934 935Here, we have overloaded the built-in ``date`` filter with a custom one. 936 937If you do the same on the ``\Twig\Environment`` itself, beware that it takes 938precedence over any other registered extensions:: 939 940 $twig = new \Twig\Environment($loader); 941 $twig->addFilter(new \Twig\TwigFilter('date', function ($timestamp, $format = 'F j, Y H:i') { 942 // do something different from the built-in date filter 943 })); 944 // the date filter will come from the above registration, not 945 // from the registered extension below 946 $twig->addExtension(new MyCoreExtension()); 947 948.. caution:: 949 950 Note that overloading the built-in Twig elements is not recommended as it 951 might be confusing. 952 953Testing an Extension 954-------------------- 955 956Functional Tests 957~~~~~~~~~~~~~~~~ 958 959You can create functional tests for extensions simply by creating the 960following file structure in your test directory:: 961 962 Fixtures/ 963 filters/ 964 foo.test 965 bar.test 966 functions/ 967 foo.test 968 bar.test 969 tags/ 970 foo.test 971 bar.test 972 IntegrationTest.php 973 974The ``IntegrationTest.php`` file should look like this:: 975 976 class Project_Tests_IntegrationTest extends \Twig\Test\IntegrationTestCase 977 { 978 public function getExtensions() 979 { 980 return [ 981 new Project_Twig_Extension1(), 982 new Project_Twig_Extension2(), 983 ]; 984 } 985 986 public function getFixturesDir() 987 { 988 return dirname(__FILE__).'/Fixtures/'; 989 } 990 } 991 992Fixtures examples can be found within the Twig repository 993`tests/Twig/Fixtures`_ directory. 994 995Node Tests 996~~~~~~~~~~ 997 998Testing the node visitors can be complex, so extend your test cases from 999``\Twig\Test\NodeTestCase``. Examples can be found in the Twig repository 1000`tests/Twig/Node`_ directory. 1001 1002.. _`rot13`: https://secure.php.net/manual/en/function.str-rot13.php 1003.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures 1004.. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node 1005