doc/advanced.rst
a855b189
 Extending Twig
 ==============
 
1918edef
 .. caution::
 
     This section describes how to extend Twig as of **Twig 1.12**. If you are
     using an older version, read the :doc:`legacy<advanced_legacy>` chapter
     instead.
 
dc884c38
 Twig can be extended in many ways; you can add extra tags, filters, tests,
 operators, global variables, and functions. You can even extend the parser
 itself with node visitors.
a855b189
 
 .. note::
 
073cfd01
     The first section of this chapter describes how to extend Twig easily. If
     you want to reuse your changes in different projects or if you want to
     share them with others, you should then create an extension as described
     in the following section.
a855b189
 
4c9e394c
 .. caution::
 
51f6974c
     When extending Twig without creating an extension, Twig won't be able to
     recompile your templates when the PHP code is updated. To see your changes
     in real-time, either disable template caching or package your code into an
     extension (see the next section of this chapter).
4c9e394c
 
dc884c38
 Before extending Twig, you must understand the differences between all the
 different possible extension points and when to use them.
a855b189
 
dc884c38
 First, remember that Twig has two main language constructs:
a855b189
 
dc884c38
 * ``{{ }}``: used to print the result of an expression evaluation;
a855b189
 
dc884c38
 * ``{% %}``: used to execute statements.
a855b189
 
dc884c38
 To understand why Twig exposes so many extension points, let's see how to
 implement a *Lorem ipsum* generator (it needs to know the number of words to
 generate).
a855b189
 
dc884c38
 You can use a ``lipsum`` *tag*:
a855b189
 
dc884c38
 .. code-block:: jinja
a855b189
 
dc884c38
     {% lipsum 40 %}
a855b189
 
dc884c38
 That works, but using a tag for ``lipsum`` is not a good idea for at least
 three main reasons:
a855b189
 
dc884c38
 * ``lipsum`` is not a language construct;
 * The tag outputs something;
 * The tag is not flexible as you cannot use it in an expression:
a855b189
 
dc884c38
   .. code-block:: jinja
 
       {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
 
 In fact, you rarely need to create tags; and that's good news because tags are
 the most complex extension point of Twig.
 
 Now, let's use a ``lipsum`` *filter*:
 
 .. code-block:: jinja
 
     {{ 40|lipsum }}
 
 Again, it works, but it looks weird. A filter transforms the passed value to
 something else but here we use the value to indicate the number of words to
68b8c461
 generate (so, ``40`` is an argument of the filter, not the value we want to
 transform).
dc884c38
 
 Next, let's use a ``lipsum`` *function*:
 
 .. code-block:: jinja
 
     {{ lipsum(40) }}
 
 Here we go. For this specific example, the creation of a function is the
 extension point to use. And you can use it anywhere an expression is accepted:
 
 .. code-block:: jinja
 
a6665053
     {{ 'some text' ~ lipsum(40) ~ 'some more text' }}
dc884c38
 
a6665053
     {% set lipsum = lipsum(40) %}
dc884c38
 
 Last but not the least, you can also use a *global* object with a method able
 to generate lorem ipsum text:
 
 .. code-block:: jinja
 
     {{ text.lipsum(40) }}
 
 As a rule of thumb, use functions for frequently used features and global
 objects for everything else.
a855b189
 
dc884c38
 Keep in mind the following when you want to extend Twig:
a855b189
 
dc884c38
 ========== ========================== ========== =========================
 What?      Implementation difficulty? How often? When?
 ========== ========================== ========== =========================
b012d2ed
 *macro*    trivial                    frequent   Content generation
 *global*   trivial                    frequent   Helper object
 *function* trivial                    frequent   Content generation
dc884c38
 *filter*   trivial                    frequent   Value transformation
 *tag*      complex                    rare       DSL language construct
 *test*     trivial                    rare       Boolean decision
 *operator* trivial                    rare       Values transformation
 ========== ========================== ========== =========================
a855b189
 
dc884c38
 Globals
 -------
 
 A global variable is like any other template variable, except that it's
 available in all templates and macros::
a855b189
 
     $twig = new Twig_Environment($loader);
dc884c38
     $twig->addGlobal('text', new Text());
a855b189
 
432b1732
 You can then use the ``text`` variable anywhere in a template:
a855b189
 
dc884c38
 .. code-block:: jinja
 
     {{ text.lipsum(40) }}
a855b189
 
dc884c38
 Filters
 -------
 
1918edef
 Creating a filter is as simple as associating a name with a PHP callable::
a855b189
 
1918edef
     // an anonymous function
     $filter = new Twig_SimpleFilter('rot13', function ($string) {
         return str_rot13($string);
     });
a855b189
 
1918edef
     // or a simple PHP function
     $filter = new Twig_SimpleFilter('rot13', 'str_rot13');
a855b189
 
9cb6860e
     // or a class static method
a2c42534
     $filter = new Twig_SimpleFilter('rot13', ['SomeClass', 'rot13Filter']);
9cb6860e
     $filter = new Twig_SimpleFilter('rot13', 'SomeClass::rot13Filter');
 
1918edef
     // or a class method
a2c42534
     $filter = new Twig_SimpleFilter('rot13', [$this, 'rot13Filter']);
9cb6860e
     // the one below needs a runtime implementation (see below for more information)
a2c42534
     $filter = new Twig_SimpleFilter('rot13', ['SomeClass', 'rot13Filter']);
a855b189
 
1918edef
 The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
 of the filter you will use in templates and the second one is the PHP callable
 to associate with it.
a855b189
 
1918edef
 Then, add the filter to your Twig environment::
a855b189
 
     $twig = new Twig_Environment($loader);
1918edef
     $twig->addFilter($filter);
a855b189
 
1918edef
 And here is how to use it in a template:
a855b189
 
 .. code-block:: jinja
 
1918edef
     {{ 'Twig'|rot13 }}
a855b189
 
2757f482
     {# will output Gjvt #}
a855b189
 
1918edef
 When called by Twig, the PHP callable receives the left side of the filter
 (before the pipe ``|``) as the first argument and the extra arguments passed
 to the filter (within parentheses ``()``) as extra arguments.
a855b189
 
1918edef
 For instance, the following code:
a855b189
 
1918edef
 .. code-block:: jinja
a855b189
 
1918edef
     {{ 'TWIG'|lower }}
     {{ now|date('d/m/Y') }}
a855b189
 
1918edef
 is compiled to something like the following::
a855b189
 
1918edef
     <?php echo strtolower('TWIG') ?>
     <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
a855b189
 
1918edef
 The ``Twig_SimpleFilter`` class takes an array of options as its last
51f6974c
 argument::
 
1918edef
     $filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
51f6974c
 
bd1cb912
 Environment-aware Filters
a855b189
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
1918edef
 If you want to access the current environment instance in your filter, set the
 ``needs_environment`` option to ``true``; Twig will pass the current
 environment as the first argument to the filter call::
a855b189
 
1918edef
     $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $string) {
a855b189
         // get the current charset for instance
         $charset = $env->getCharset();
 
         return str_rot13($string);
a2c42534
     }, ['needs_environment' => true]);
a855b189
 
bd1cb912
 Context-aware Filters
51f6974c
 ~~~~~~~~~~~~~~~~~~~~~
 
 If you want to access the current context in your filter, set the
 ``needs_context`` option to ``true``; Twig will pass the current context as
 the first argument to the filter call (or the second one if
 ``needs_environment`` is also set to ``true``)::
 
1918edef
     $filter = new Twig_SimpleFilter('rot13', function ($context, $string) {
         // ...
a2c42534
     }, ['needs_context' => true]);
1918edef
 
     $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $context, $string) {
         // ...
a2c42534
     }, ['needs_context' => true, 'needs_environment' => true]);
51f6974c
 
a855b189
 Automatic Escaping
 ~~~~~~~~~~~~~~~~~~
 
 If automatic escaping is enabled, the output of the filter may be escaped
a21df952
 before printing. If your filter acts as an escaper (or explicitly outputs HTML
1918edef
 or JavaScript code), you will want the raw output to be printed. In such a
a855b189
 case, set the ``is_safe`` option::
 
a2c42534
     $filter = new Twig_SimpleFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);
a855b189
 
f8406701
 Some filters may need to work on input that is already escaped or safe, for
a21df952
 example when adding (safe) HTML tags to originally unsafe output. In such a
f8406701
 case, set the ``pre_escape`` option to escape the input data before it is run
 through your filter::
a855b189
 
a2c42534
     $filter = new Twig_SimpleFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);
a855b189
 
c78656a8
 Variadic Filters
 ~~~~~~~~~~~~~~~~
 
 .. versionadded:: 1.19
     Support for variadic filters was added in Twig 1.19.
 
0828c034
 When a filter should accept an arbitrary number of arguments, set the
 ``is_variadic`` option to ``true``; Twig will pass the extra arguments as the
 last argument to the filter call as an array::
c78656a8
 
a2c42534
     $filter = new Twig_SimpleFilter('thumbnail', function ($file, array $options = []) {
0828c034
         // ...
a2c42534
     }, ['is_variadic' => true]);
c78656a8
 
0828c034
 Be warned that named arguments passed to a variadic filter cannot be checked
 for validity as they will automatically end up in the option array.
c78656a8
 
4f19c7a0
 Dynamic Filters
 ~~~~~~~~~~~~~~~
 
 A filter name containing the special ``*`` character is a dynamic filter as
 the ``*`` can be any string::
 
1918edef
     $filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) {
4f19c7a0
         // ...
1918edef
     });
4f19c7a0
 
 The following filters will be matched by the above defined dynamic filter:
 
 * ``product_path``
 * ``category_path``
 
 A dynamic filter can define more than one dynamic parts::
 
e142fcad
     $filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {
4f19c7a0
         // ...
1918edef
     });
4f19c7a0
 
e3b9e99d
 The filter will receive all dynamic part values before the normal filter
1918edef
 arguments, but after the environment and the context. For instance, a call to
 ``'foo'|a_path_b()`` will result in the following arguments to be passed to
 the filter: ``('a', 'b', 'foo')``.
4f19c7a0
 
bd87ba8a
 Deprecated Filters
 ~~~~~~~~~~~~~~~~~~
 
 .. versionadded:: 1.21
     Support for deprecated filters was added in Twig 1.21.
 
 You can mark a filter as being deprecated by setting the ``deprecated`` option
 to ``true``. You can also give an alternative filter that replaces the
 deprecated one when that makes sense::
 
     $filter = new Twig_SimpleFilter('obsolete', function () {
         // ...
a2c42534
     }, ['deprecated' => true, 'alternative' => 'new_one']);
bd87ba8a
 
 When a filter is deprecated, Twig emits a deprecation notice when compiling a
 template using it. See :ref:`deprecation-notices` for more information.
 
9528bac1
 Functions
 ---------
 
1918edef
 Functions are defined in the exact same way as filters, but you need to create
 an instance of ``Twig_SimpleFunction``::
c178c281
 
     $twig = new Twig_Environment($loader);
1918edef
     $function = new Twig_SimpleFunction('function_name', function () {
4f19c7a0
         // ...
1918edef
     });
     $twig->addFunction($function);
4f19c7a0
 
1918edef
 Functions support the same features as filters, except for the ``pre_escape``
 and ``preserves_safety`` options.
4f19c7a0
 
1918edef
 Tests
 -----
4f19c7a0
 
1918edef
 Tests are defined in the exact same way as filters and functions, but you need
 to create an instance of ``Twig_SimpleTest``::
4f19c7a0
 
1918edef
     $twig = new Twig_Environment($loader);
     $test = new Twig_SimpleTest('test_name', function () {
4f19c7a0
         // ...
1918edef
     });
     $twig->addTest($test);
4f19c7a0
 
da06f590
 Tests allow you to create custom application specific logic for evaluating
a21df952
 boolean conditions. As a simple example, let's create a Twig test that checks if
da06f590
 objects are 'red'::
 
65b3799d
     $twig = new Twig_Environment($loader);
da06f590
     $test = new Twig_SimpleTest('red', function ($value) {
         if (isset($value->color) && $value->color == 'red') {
             return true;
         }
         if (isset($value->paint) && $value->paint == 'red') {
             return true;
         }
         return false;
     });
     $twig->addTest($test);
 
 Test functions should always return true/false.
 
 When creating tests you can use the ``node_class`` option to provide custom test
 compilation. This is useful if your test can be compiled into PHP primitives.
 This is used by many of the tests built into Twig::
 
65b3799d
     $twig = new Twig_Environment($loader);
da06f590
     $test = new Twig_SimpleTest(
         'odd',
         null,
a2c42534
         ['node_class' => 'Twig_Node_Expression_Test_Odd']);
da06f590
     $twig->addTest($test);
 
     class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
     {
         public function compile(Twig_Compiler $compiler)
         {
             $compiler
                 ->raw('(')
                 ->subcompile($this->getNode('node'))
                 ->raw(' % 2 == 1')
                 ->raw(')')
             ;
         }
     }
 
bd1cb912
 The above example shows how you can create tests that use a node class. The
da06f590
 node class has access to one sub-node called 'node'. This sub-node contains the
bd1cb912
 value that is being tested. When the ``odd`` filter is used in code such as:
da06f590
 
 .. code-block:: jinja
 
     {% if my_value is odd %}
 
bd1cb912
 The ``node`` sub-node will contain an expression of ``my_value``. Node-based
da06f590
 tests also have access to the ``arguments`` node. This node will contain the
 various other arguments that have been provided to your test.
4f19c7a0
 
4beb7bfb
 .. versionadded:: 1.36
     Dynamic tests support was added in Twig 1.36.
 
0828c034
 If you want to pass a variable number of positional or named arguments to the
4beb7bfb
 test, set the ``is_variadic`` option to ``true``. Tests support dynamic
 names (see dynamic filters and functions for the syntax).
c78656a8
 
dc884c38
 Tags
 ----
a855b189
 
bd1cb912
 One of the most exciting features of a template engine like Twig is the
dc884c38
 possibility to define new language constructs. This is also the most complex
 feature as you need to understand how Twig's internals work.
a855b189
 
 Let's create a simple ``set`` tag that allows the definition of simple
 variables from within a template. The tag can be used like follows:
 
 .. code-block:: jinja
 
     {% set name = "value" %}
 
     {{ name }}
 
     {# should output value #}
 
 .. note::
 
     The ``set`` tag is part of the Core extension and as such is always
     available. The built-in version is slightly more powerful and supports
     multiple assignments by default (cf. the template designers chapter for
     more information).
 
 Three steps are needed to define a new tag:
 
 * Defining a Token Parser class (responsible for parsing the template code);
 
 * Defining a Node class (responsible for converting the parsed code to PHP);
 
dc884c38
 * Registering the tag.
a855b189
 
 Registering a new tag
 ~~~~~~~~~~~~~~~~~~~~~
 
dc884c38
 Adding a tag is as simple as calling the ``addTokenParser`` method on the
 ``Twig_Environment`` instance::
a855b189
 
dc884c38
     $twig = new Twig_Environment($loader);
     $twig->addTokenParser(new Project_Set_TokenParser());
a855b189
 
 Defining a Token Parser
 ~~~~~~~~~~~~~~~~~~~~~~~
 
 Now, let's see the actual code of this class::
 
     class Project_Set_TokenParser extends Twig_TokenParser
     {
         public function parse(Twig_Token $token)
         {
6a25c5db
             $parser = $this->parser;
             $stream = $parser->getStream();
a855b189
 
6a25c5db
             $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
             $stream->expect(Twig_Token::OPERATOR_TYPE, '=');
             $value = $parser->getExpressionParser()->parseExpression();
             $stream->expect(Twig_Token::BLOCK_END_TYPE);
a855b189
 
6a25c5db
             return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
7e8df568
         }
a855b189
 
         public function getTag()
         {
             return 'set';
         }
     }
 
 The ``getTag()`` method must return the tag we want to parse, here ``set``.
 
 The ``parse()`` method is invoked whenever the parser encounters a ``set``
 tag. It should return a ``Twig_Node`` instance that represents the node (the
 ``Project_Set_Node`` calls creating is explained in the next section).
 
 The parsing process is simplified thanks to a bunch of methods you can call
 from the token stream (``$this->parser->getStream()``):
 
648c8435
 * ``getCurrent()``: Gets the current token in the stream.
a855b189
 
648c8435
 * ``next()``: Moves to the next token in the stream, *but returns the old one*.
a855b189
 
648c8435
 * ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
   the current token is of a particular type or value (or both). The value may be an
   array of several possible values.
 
 * ``expect($type[, $value[, $message]])``: If the current token isn't of the given
   type/value a syntax error is thrown. Otherwise, if the type and value are correct,
   the token is returned and the stream moves to the next token.
a855b189
 
a264fc62
 * ``look()``: Looks at the next token without consuming it.
d72b6ad1
 
a855b189
 Parsing expressions is done by calling the ``parseExpression()`` like we did for
 the ``set`` tag.
 
 .. tip::
 
     Reading the existing ``TokenParser`` classes is the best way to learn all
     the nitty-gritty details of the parsing process.
 
 Defining a Node
 ~~~~~~~~~~~~~~~
 
 The ``Project_Set_Node`` class itself is rather simple::
 
     class Project_Set_Node extends Twig_Node
     {
a4daa4ca
         public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
a855b189
         {
a2c42534
             parent::__construct(['value' => $value], ['name' => $name], $line, $tag);
7e8df568
         }
a855b189
 
27f31553
         public function compile(Twig_Compiler $compiler)
a855b189
         {
             $compiler
                 ->addDebugInfo($this)
                 ->write('$context[\''.$this->getAttribute('name').'\'] = ')
                 ->subcompile($this->getNode('value'))
                 ->raw(";\n")
             ;
         }
     }
 
 The compiler implements a fluid interface and provides methods that helps the
 developer generate beautiful and readable PHP code:
 
 * ``subcompile()``: Compiles a node.
 
 * ``raw()``: Writes the given string as is.
 
 * ``write()``: Writes the given string by adding indentation at the beginning
   of each line.
 
 * ``string()``: Writes a quoted string.
 
 * ``repr()``: Writes a PHP representation of a given value (see
   ``Twig_Node_For`` for a usage example).
 
 * ``addDebugInfo()``: Adds the line of the original template file related to
   the current node as a comment.
 
 * ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
   usage example).
 
 * ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
   usage example).
 
5923f0cb
 .. _creating_extensions:
 
 Creating an Extension
 ---------------------
db8a1094
 
 The main motivation for writing an extension is to move often used code into a
 reusable class like adding support for internationalization. An extension can
 define tags, filters, tests, operators, global variables, functions, and node
 visitors.
 
 Most of the time, it is useful to create a single extension for your project,
 to host all the specific tags and filters you want to add to Twig.
 
4c9e394c
 .. tip::
 
     When packaging your code into an extension, Twig is smart enough to
1918edef
     recompile your templates whenever you make a change to it (when
4c9e394c
     ``auto_reload`` is enabled).
 
db8a1094
 .. note::
 
     Before writing your own extensions, have a look at the Twig official
4f57c6ea
     extension repository: https://github.com/twigphp/Twig-extensions.
db8a1094
 
 An extension is a class that implements the following interface::
 
     interface Twig_ExtensionInterface
     {
         /**
          * Initializes the runtime environment.
          *
          * This is where you can load some file that contains filter functions for instance.
          *
57cd302f
          * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead
db8a1094
          */
         function initRuntime(Twig_Environment $environment);
 
         /**
          * Returns the token parser instances to add to the existing list.
          *
f21c44cd
          * @return (Twig_TokenParserInterface|Twig_TokenParserBrokerInterface)[]
db8a1094
          */
         function getTokenParsers();
 
         /**
          * Returns the node visitor instances to add to the existing list.
          *
f21c44cd
          * @return Twig_NodeVisitorInterface[]
db8a1094
          */
         function getNodeVisitors();
 
         /**
          * Returns a list of filters to add to the existing list.
          *
f21c44cd
          * @return Twig_SimpleFilter[]
db8a1094
          */
         function getFilters();
 
         /**
          * Returns a list of tests to add to the existing list.
          *
f21c44cd
          * @return Twig_SimpleTest[]
db8a1094
          */
         function getTests();
 
         /**
          * Returns a list of functions to add to the existing list.
          *
f21c44cd
          * @return Twig_SimpleFunction[]
db8a1094
          */
         function getFunctions();
 
         /**
          * Returns a list of operators to add to the existing list.
          *
282df533
          * @return array<array> First array of unary operators, second array of binary operators
db8a1094
          */
         function getOperators();
 
         /**
          * Returns a list of global variables to add to the existing list.
          *
          * @return array An array of global variables
e3a325f2
          *
2660286d
          * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead
db8a1094
          */
         function getGlobals();
 
         /**
          * Returns the name of the extension.
          *
          * @return string The extension name
71f03df0
          *
          * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
db8a1094
          */
         function getName();
     }
 
71f03df0
 To keep your extension class clean and lean, inherit from the built-in
 ``Twig_Extension`` class instead of implementing the interface as it provides
 empty implementations for all methods:
db8a1094
 
     class Project_Twig_Extension extends Twig_Extension
     {
     }
 
71f03df0
 Of course, this extension does nothing for now. We will customize it in the
 next sections.
 
db8a1094
 .. note::
 
71f03df0
     Prior to Twig 1.26, you must implement the ``getName()`` method which must
     return a unique identifier for the extension.
db8a1094
 
 Twig does not care where you save your extension on the filesystem, as all
 extensions must be registered explicitly to be available in your templates.
 
 You can register an extension by using the ``addExtension()`` method on your
 main ``Environment`` object::
 
     $twig = new Twig_Environment($loader);
     $twig->addExtension(new Project_Twig_Extension());
 
 .. tip::
 
9cb6860e
     The Twig core extensions are great examples of how extensions work.
db8a1094
 
 Globals
 ~~~~~~~
 
 Global variables can be registered in an extension via the ``getGlobals()``
 method::
 
b8a987ee
     class Project_Twig_Extension extends Twig_Extension implements Twig_Extension_GlobalsInterface
db8a1094
     {
         public function getGlobals()
         {
a2c42534
             return [
db8a1094
                 'text' => new Text(),
a2c42534
             ];
db8a1094
         }
 
         // ...
     }
 
 Functions
 ~~~~~~~~~
 
 Functions can be registered in an extension via the ``getFunctions()``
 method::
 
     class Project_Twig_Extension extends Twig_Extension
     {
         public function getFunctions()
         {
a2c42534
             return [
1918edef
                 new Twig_SimpleFunction('lipsum', 'generate_lipsum'),
a2c42534
             ];
db8a1094
         }
 
         // ...
     }
 
 Filters
 ~~~~~~~
 
 To add a filter to an extension, you need to override the ``getFilters()``
 method. This method must return an array of filters to add to the Twig
 environment::
 
     class Project_Twig_Extension extends Twig_Extension
     {
         public function getFilters()
         {
a2c42534
             return [
1918edef
                 new Twig_SimpleFilter('rot13', 'str_rot13'),
a2c42534
             ];
db8a1094
         }
 
         // ...
     }
 
 Tags
 ~~~~
 
 Adding a tag in an extension can be done by overriding the
 ``getTokenParsers()`` method. This method must return an array of tags to add
 to the Twig environment::
 
     class Project_Twig_Extension extends Twig_Extension
     {
         public function getTokenParsers()
         {
a2c42534
             return [new Project_Set_TokenParser()];
db8a1094
         }
 
         // ...
     }
 
 In the above code, we have added a single new tag, defined by the
 ``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
 responsible for parsing the tag and compiling it to PHP.
 
 Operators
 ~~~~~~~~~
 
bd1cb912
 The ``getOperators()`` methods lets you add new operators. Here is how to add
db8a1094
 ``!``, ``||``, and ``&&`` operators::
 
     class Project_Twig_Extension extends Twig_Extension
     {
         public function getOperators()
         {
a2c42534
             return [
                 [
                     '!' => ['precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'],
                 ],
                 [
                     '||' => ['precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
                     '&&' => ['precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
                 ],
             ];
db8a1094
         }
 
         // ...
     }
 
 Tests
 ~~~~~
 
bd1cb912
 The ``getTests()`` method lets you add new test functions::
db8a1094
 
     class Project_Twig_Extension extends Twig_Extension
     {
         public function getTests()
         {
a2c42534
             return [
1918edef
                 new Twig_SimpleTest('even', 'twig_test_even'),
a2c42534
             ];
db8a1094
         }
 
         // ...
     }
 
9cb6860e
 Definition vs Runtime
 ~~~~~~~~~~~~~~~~~~~~~
 
 Twig filters, functions, and tests runtime implementations can be defined as
 any valid PHP callable:
 
 * **functions/static methods**: Simple to implement and fast (used by all Twig
   core extensions); but it is hard for the runtime to depend on external
   objects;
 
 * **closures**: Simple to implement;
 
 * **object methods**: More flexible and required if your runtime code depends
   on external objects.
 
 The simplest way to use methods is to define them on the extension itself::
 
     class Project_Twig_Extension extends Twig_Extension
     {
         private $rot13Provider;
 
         public function __construct($rot13Provider)
         {
             $this->rot13Provider = $rot13Provider;
         }
 
         public function getFunctions()
         {
a2c42534
             return [
                 new Twig_SimpleFunction('rot13', [$this, 'rot13']),
             ];
9cb6860e
         }
 
         public function rot13($value)
         {
b42fd643
             return $this->rot13Provider->rot13($value);
9cb6860e
         }
     }
 
 This is very convenient but not recommended as it makes template compilation
 depend on runtime dependencies even if they are not needed (think for instance
 as a dependency that connects to a database engine).
 
 As of Twig 1.26, you can easily decouple the extension definitions from their
 runtime implementations by registering a ``Twig_RuntimeLoaderInterface``
 instance on the environment that knows how to instantiate such runtime classes
 (runtime classes must be autoload-able)::
 
     class RuntimeLoader implements Twig_RuntimeLoaderInterface
     {
         public function load($class)
         {
             // implement the logic to create an instance of $class
             // and inject its dependencies
             // most of the time, it means using your dependency injection container
             if ('Project_Twig_RuntimeExtension' === $class) {
                 return new $class(new Rot13Provider());
             } else {
                 // ...
             }
         }
     }
 
     $twig->addRuntimeLoader(new RuntimeLoader());
 
7efe539b
 .. note::
 
     As of Twig 1.32, Twig comes with a PSR-11 compatible runtime loader
     (``Twig_ContainerRuntimeLoader``) that works on PHP 5.3+.
 
9cb6860e
 It is now possible to move the runtime logic to a new
 ``Project_Twig_RuntimeExtension`` class and use it directly in the extension::
 
c0b5eb9f
     class Project_Twig_RuntimeExtension
9cb6860e
     {
         private $rot13Provider;
 
         public function __construct($rot13Provider)
         {
             $this->rot13Provider = $rot13Provider;
         }
 
         public function rot13($value)
         {
b42fd643
             return $this->rot13Provider->rot13($value);
9cb6860e
         }
     }
 
     class Project_Twig_Extension extends Twig_Extension
     {
         public function getFunctions()
         {
a2c42534
             return [
                 new Twig_SimpleFunction('rot13', ['Project_Twig_RuntimeExtension', 'rot13']),
9cb6860e
                 // or
                 new Twig_SimpleFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
a2c42534
             ];
9cb6860e
         }
     }
 
51f6974c
 Overloading
 -----------
 
 To overload an already defined filter, test, operator, global variable, or
0559cacf
 function, re-define it in an extension and register it **as late as
 possible** (order matters)::
51f6974c
 
     class MyCoreExtension extends Twig_Extension
     {
         public function getFilters()
         {
a2c42534
             return [
                 new Twig_SimpleFilter('date', [$this, 'dateFilter']),
             ];
51f6974c
         }
 
         public function dateFilter($timestamp, $format = 'F j, Y H:i')
         {
             // do something different from the built-in date filter
         }
     }
 
     $twig = new Twig_Environment($loader);
     $twig->addExtension(new MyCoreExtension());
 
0559cacf
 Here, we have overloaded the built-in ``date`` filter with a custom one.
 
9cb6860e
 If you do the same on the ``Twig_Environment`` itself, beware that it takes
0559cacf
 precedence over any other registered extensions::
 
     $twig = new Twig_Environment($loader);
     $twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
         // do something different from the built-in date filter
     }));
     // the date filter will come from the above registration, not
     // from the registered extension below
     $twig->addExtension(new MyCoreExtension());
 
51f6974c
 .. caution::
 
     Note that overloading the built-in Twig elements is not recommended as it
     might be confusing.
 
66a8c0b8
 Testing an Extension
 --------------------
 
36372c61
 Functional Tests
 ~~~~~~~~~~~~~~~~
 
66a8c0b8
 You can create functional tests for extensions simply by creating the
 following file structure in your test directory::
36372c61
 
     Fixtures/
         filters/
             foo.test
             bar.test
         functions/
             foo.test
             bar.test
         tags/
             foo.test
             bar.test
     IntegrationTest.php
 
 The ``IntegrationTest.php`` file should look like this::
 
     class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
     {
         public function getExtensions()
         {
a2c42534
             return [
36372c61
                 new Project_Twig_Extension1(),
                 new Project_Twig_Extension2(),
a2c42534
             ];
36372c61
         }
 
         public function getFixturesDir()
         {
             return dirname(__FILE__).'/Fixtures/';
         }
     }
 
66a8c0b8
 Fixtures examples can be found within the Twig repository
 `tests/Twig/Fixtures`_ directory.
36372c61
 
 Node Tests
 ~~~~~~~~~~
 
 Testing the node visitors can be complex, so extend your test cases from
66a8c0b8
 ``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
 `tests/Twig/Node`_ directory.
36372c61
 
4f57c6ea
 .. _`rot13`:                   https://secure.php.net/manual/en/function.str-rot13.php
e37fc6b0
 .. _`tests/Twig/Fixtures`:     https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Fixtures
 .. _`tests/Twig/Node`:         https://github.com/twigphp/Twig/tree/master/test/Twig/Tests/Node