rewrote the advanced chapter and documented globals and functions
authorFabien Potencier <fabien.potencier@gmail.com>
Sun, 19 Dec 2010 18:56:14 +0000 (19:56 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Sun, 19 Dec 2010 19:11:00 +0000 (20:11 +0100)
doc/advanced.rst
doc/extensions.rst [new file with mode: 0644]

index 696d407..97a5618 100644 (file)
 Extending Twig
 ==============
 
-Twig supports extensions that can add extra tags, filters, or even extend the
-parser itself with node visitor classes. The main motivation for writing
-an extension is to move often used code into a reusable class like adding
-support for internationalization.
-
-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.
+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.
 
 .. note::
 
-    Before writing your own extensions, have a look at the Twig official
-    extension repository: http://github.com/fabpot/Twig-extensions.
+    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 next
+    chapter.
 
-Extending without an Extension (new in Twig 0.9.7)
---------------------------------------------------
+Before extending Twig, you must understand the differences between all the
+different possible extension points and when to use them.
 
-If you just need to register a small amount of tags and/or filters, you can
-register them without creating an extension::
+First, remember that Twig has two main language constructs:
 
-    $twig = new Twig_Environment($loader);
-    $twig->addTokenParser(new CustomTokenParser());
-    $twig->addFilter('upper', new Twig_Filter_Function('strtoupper'));
+* ``{{ }}``: used to print the result of an expression evaluation;
 
-Anatomy of an Extension
------------------------
+* ``{% %}``: used to execute statements.
 
-An extension is a class that implements the following interface::
+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).
 
-    interface Twig_ExtensionInterface
-    {
-        /**
-         * Initializes the runtime environment.
-         *
-         * This is where you can load some file that contains filter functions for instance.
-         */
-        function initRuntime();
-
-        /**
-         * Returns the token parser instances to add to the existing list.
-         *
-         * @return array An array of Twig_TokenParser instances
-         */
-        function getTokenParsers();
-
-        /**
-         * Returns the node visitor instances to add to the existing list.
-         *
-         * @return array An array of Twig_NodeVisitorInterface instances
-         */
-        function getNodeVisitors();
-
-        /**
-         * Returns a list of filters to add to the existing list.
-         *
-         * @return array An array of filters
-         */
-        function getFilters();
-
-        /**
-         * Returns a list of tests to add to the existing list.
-         *
-         * @return array An array of tests
-         */
-        function getTests();
-
-        /**
-         * Returns the name of the extension.
-         *
-         * @return string The extension name
-         */
-        function getName();
-    }
+You can use a ``lipsum`` *tag*:
 
-To keep your extension class clean and lean, it can inherit from the built-in
-``Twig_Extension`` class instead of implementing the whole interface. That
-way, you just need to implement the ``getName()`` method as the
-``Twig_Extension`` provides empty implementations for all other methods.
+.. code-block:: jinja
 
-The ``getName()`` method must return a unique identifier for your extension.
+    {% lipsum 40 %}
 
-Now, with this information in mind, let's create the most basic extension
-possible::
+That works, but using a tag for ``lipsum`` is not a good idea for at least
+three main reasons:
 
-    class Project_Twig_Extension extends Twig_Extension
-    {
-        public function getName()
-        {
-            return 'project';
-        }
-    }
+* ``lipsum`` is not a language construct;
+* The tag outputs something;
+* The tag is not flexible as you cannot use it in an expression:
 
-.. note::
+  .. 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
+generate.
+
+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
+
+    {{ 'some text' ~ ipsum(40) ~ 'some more text' }}
+
+    {% set ipsum = ipsum(40) %}
+
+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.
 
-    Of course, this extension does nothing for now. We will add tags and
-    filters in the coming sections.
+Keep in mind the following when you want to extend Twig:
 
-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.
+========== ========================== ========== =========================
+What?      Implementation difficulty? How often? When?
+========== ========================== ========== =========================
+*global*   trivial                    frequent   Content generation
+*function* trivial                    frequent   Content generation (core)
+*filter*   trivial                    frequent   Value transformation
+*tag*      complex                    rare       DSL language construct
+*test*     trivial                    rare       Boolean decision
+*operator* trivial                    rare       Values transformation
+========== ========================== ========== =========================
 
-You can register an extension by using the ``addExtension()`` method on your
-main ``Environment`` object::
+Globals
+-------
+
+A global variable is like any other template variable, except that it's
+available in all templates and macros::
 
     $twig = new Twig_Environment($loader);
-    $twig->addExtension(new Project_Twig_Extension());
+    $twig->addGlobal('text', new Text());
 
-Of course, you need to first load the extension file by either using
-``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
+You can then use the ``user`` variable anywhere in a template:
 
-.. tip::
+.. code-block:: jinja
+
+    {{ text.lipsum(40) }}
 
-    The bundled extensions are great examples of how extensions work.
+Functions
+---------
 
-Defining new Filters
---------------------
+A function is a variable that is callable. The value is an instance of
+``Twig_Function``::
 
-.. caution::
+    $twig = new Twig_Environment($loader);
+    $twig->addGlobal('lipsum', new Twig_Function(new Text(), 'getLipsum'));
 
-    This section describes the creation of new filters for Twig 0.9.5 and
-    above.
+    // or
+    $twig->addGlobal('text', new Text());
+    $twig->addGlobal('lipsum', new Twig_Function('text', 'getLipsum'));
 
-The most common element you will want to add to Twig is filters. A filter is
-just a regular PHP function or a method that takes the left side of the filter
-(before the pipe ``|``) as first argument and the extra arguments passed to
-the filter (within parentheses ``()``) as extra arguments.
+The first argument to ``Twig_Function`` is an object or a variable name
+referencing an object.
 
-For instance, let's say you have the following code in a template:
+You can then use the ``lipsum`` function anywhere in a template:
+
+.. code-block:: jinja
+
+    {{ lipsum(40) }}
+
+.. note::
+
+    A function is not necessarily a global variable.
+
+Filters
+-------
+
+A filter is a regular PHP function or an object method that takes the left
+side of the filter (before the pipe ``|``) as first argument and the extra
+arguments passed to the filter (within parentheses ``()``) as extra arguments.
+
+Defining a filter is as easy as associating the filter name with a PHP
+callable. For instance, let's say you have the following code in a template:
 
 .. code-block:: jinja
 
     {{ 'TWIG'|lower }}
 
-When compiling this template to PHP, Twig will first look for the PHP function
+When compiling this template to PHP, Twig looks for the PHP callable
 associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig
 filter, and it is simply mapped to the PHP ``strtolower()`` function. After
-compilation, the generated PHP code is roughly equivalent to::
+compilation, the generated PHP code is roughly equivalent to:
+
+.. code-block:: html+php
 
     <?php echo strtolower('TWIG') ?>
 
@@ -149,12 +167,11 @@ A filter can also take extra arguments like in the following example:
     {{ now|date('d/m/Y') }}
 
 In this case, the extra arguments are passed to the function after the main
-argument, and the compiled code is equivalent to::
+argument, and the compiled code is equivalent to:
 
-    <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
+.. code-block:: html+php
 
-Function Filters
-~~~~~~~~~~~~~~~~
+    <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
 
 Let's see how to create a new filter.
 
@@ -168,42 +185,16 @@ expected output:
 
     {# should displays Gjvt #}
 
-Adding a filter is as simple as calling the ``addFilter`` method of the
-``Twig_Environment`` instance (new in Twig 0.9.7)::
+Adding a filter is as simple as calling the ``addFilter()`` method on the
+``Twig_Environment`` instance::
 
     $twig = new Twig_Environment($loader);
-    $twig->addFilter('upper', new Twig_Filter_Function('strtoupper'));
+    $twig->addFilter('rot13', new Twig_Filter_Function('rot13'));
 
-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()
-        {
-            return array(
-                'rot13' => new Twig_Filter_Function('str_rot13'),
-            );
-        }
-
-        public function getName()
-        {
-            return 'project';
-        }
-    }
-
-As you can see in the above code, the ``getFilters()`` method returns an array
-where keys are the name of the filters (``rot13``) and the values the
-definition of the filter (``new Twig_Filter_Function('str_rot13')``).
-
-The definition of a filter is always an object. For this first example, we
-have defined a filter as an object of the ``Twig_Filter_Function`` class.
-
-The ``Twig_Filter_Function`` class is to be used when you need to define a
-filter implemented as a function. The first argument passed to the
-``Twig_Filter_Function`` constructor is the name of the function to call, here
-``str_rot13``, a native PHP function.
+The second argument of ``addFilter()`` is an instance of ``Twig_Filter``.
+Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The
+first argument passed to the ``Twig_Filter_Function`` constructor is the name
+of the PHP function to call, here ``str_rot13``, a native PHP function.
 
 Let's say I now want to be able to add a prefix before the converted string:
 
@@ -224,93 +215,20 @@ create a new PHP function::
 As you can see, the ``prefix`` argument of the filter is passed as an extra
 argument to the ``project_compute_rot13()`` function.
 
-.. note::
+Adding this filter is as easy as before::
 
-    This function can declared anywhere by it is a good idea to define it in
-    the same file as the extension class.
+    $twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13'));
 
-The new extension code looks very similar to the previous one::
+For better encapsulation, a filter can also be defined as a static method of a
+class. The ``Twig_Filter_Function`` class can also be used to register such
+static methods as filters::
 
-    class Project_Twig_Extension extends Twig_Extension
-    {
-        public function getFilters()
-        {
-            return array(
-                'rot13' => new Twig_Filter_Function('project_compute_rot13'),
-            );
-        }
-
-        public function getName()
-        {
-            return 'project';
-        }
-    }
-
-Class Method Filters
-~~~~~~~~~~~~~~~~~~~~
-
-Instead of creating a function to define a filter as we have done before, you
-can also create a static method in a class for better encapsulation.
-
-The ``Twig_Filter_Function`` class can also be used to register such static
-methods as filters::
-
-    class Project_Twig_Extension extends Twig_Extension
-    {
-        public function getFilters()
-        {
-            return array(
-                'rot13' => new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'),
-            );
-        }
-
-        static public function rot13Filter($string)
-        {
-            return str_rot13($string);
-        }
-
-        public function getName()
-        {
-            return 'project';
-        }
-    }
-
-Object Method Filters
-~~~~~~~~~~~~~~~~~~~~~
-
-Defining static methods is one step towards better encapsulation, but defining
-filters as methods of your extension class is probably the best solution.
-
-This is possible by using ``Twig_Filter_Method`` instead of
-``Twig_Filter_Function`` when defining a filter::
-
-    class Project_Twig_Extension extends Twig_Extension
-    {
-        public function getFilters()
-        {
-            return array(
-                'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
-            );
-        }
-
-        public function rot13Filter($string)
-        {
-            return str_rot13($string);
-        }
-
-        public function getName()
-        {
-            return 'project';
-        }
-    }
+    $twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter'));
 
-The first argument of the ``Twig_Filter_Method`` constructor is always
-``$this``, the current extension object. The second one is the name of the
-method to call.
+.. tip::
 
-Using methods for filters is a great way to package your filter without
-polluting the global namespace. This also gives the developer more flexibility
-at the cost of a small overhead.
+    In an extension, you can also define a filter as a static method of the
+    extension class.
 
 Environment aware Filters
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -342,65 +260,17 @@ case, set the ``is_safe`` option::
 
     $filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
 
-Some advanced filters may have to work on already escaped or safe values. In
-such a case, set the ``pre_escape`` option::
+Some filters may have to work on already escaped or safe values. In such a
+case, set the ``pre_escape`` option::
 
     $filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
 
-Overriding default Filters
---------------------------
-
-.. caution::
-
-    This section describes how to override default filters for Twig 0.9.5 and
-    above.
-
-If some default core filters do not suit your needs, you can easily override
-them by creating your own core extension. Of course, you don't need to copy
-and paste the whole core extension code of Twig. Instead, you can just extends
-it and override the filter(s) you want by overriding the ``getFilters()``
-method::
-
-    class MyCoreExtension extends Twig_Extension_Core
-    {
-        public function getFilters()
-        {
-            return array_merge(
-                parent::getFilters(),
-                array(
-                    'date' => Twig_Filter_Method($this, 'dateFilter')
-                )
-            );
-      }
-
-        public function dateFilter($timestamp, $format = 'F j, Y H:i')
-        {
-            return '...'.twig_date_format_filter($timestamp, $format);
-        }
-    }
-
-Here, we override the ``date`` filter with a custom one. Using this new core
-extension is as simple as registering the ``MyCoreExtension`` extension by
-calling the ``addExtension()`` method on the environment instance::
-
-    $twig = new Twig_Environment($loader);
-    $twig->addExtension(new MyCoreExtension());
-
-But I can already hear some people wondering how it can work as the Core
-extension is loaded by default. That's true, but the trick is that both
-extensions share the same unique identifier (``core`` - defined in the
-``getName()`` method). By registering an extension with the same name as an
-existing one, you have actually overridden the default one, even if it is
-already registered::
-
-    $twig->addExtension(new Twig_Extension_Core());
-    $twig->addExtension(new MyCoreExtension());
-
-Defining new Tags
------------------
+Tags
+----
 
 One of the most exciting feature of a template engine like Twig is the
-possibility to define new language constructs.
+possibility to define new language constructs. This is also the most complex
+feature as you need to understand how Twig's internals work.
 
 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:
@@ -426,28 +296,16 @@ Three steps are needed to define a new tag:
 
 * Defining a Node class (responsible for converting the parsed code to PHP);
 
-* Registering the tag in an extension.
+* Registering the tag.
 
 Registering a new tag
 ~~~~~~~~~~~~~~~~~~~~~
 
-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::
+Adding a tag is as simple as calling the ``addTokenParser`` method on the
+``Twig_Environment`` instance::
 
-    class Project_Twig_Extension extends Twig_Extension
-    {
-        public function getTokenParsers()
-        {
-            return array(new Project_Set_TokenParser());
-        }
-
-        // ...
-    }
-
-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.
+    $twig = new Twig_Environment($loader);
+    $twig->addTokenParser(new Project_Set_TokenParser());
 
 Defining a Token Parser
 ~~~~~~~~~~~~~~~~~~~~~~~
@@ -549,10 +407,4 @@ developer generate beautiful and readable PHP code:
 * ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
   usage example).
 
-Creating a Node Visitor
------------------------
-
-To be written...
-
-.. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register
 .. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
diff --git a/doc/extensions.rst b/doc/extensions.rst
new file mode 100644 (file)
index 0000000..1c4079b
--- /dev/null
@@ -0,0 +1,302 @@
+Creating a Twig Extension
+=========================
+
+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.
+
+.. note::
+
+    Before writing your own extensions, have a look at the Twig official
+    extension repository: http://github.com/fabpot/Twig-extensions.
+
+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.
+         *
+         * @param Twig_Environment $environement The current Twig_Environment instance
+         */
+        public function initRuntime(Twig_Environment $environement);
+
+        /**
+         * Returns the token parser instances to add to the existing list.
+         *
+         * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
+         */
+        public function getTokenParsers();
+
+        /**
+         * Returns the node visitor instances to add to the existing list.
+         *
+         * @return array An array of Twig_NodeVisitorInterface instances
+         */
+        public function getNodeVisitors();
+
+        /**
+         * Returns a list of filters to add to the existing list.
+         *
+         * @return array An array of filters
+         */
+        public function getFilters();
+
+        /**
+         * Returns a list of tests to add to the existing list.
+         *
+         * @return array An array of tests
+         */
+        public function getTests();
+
+        /**
+         * Returns a list of operators to add to the existing list.
+         *
+         * @return array An array of operators
+         */
+        public function getOperators();
+
+        /**
+         * Returns a list of global functions to add to the existing list.
+         *
+         * @return array An array of global functions
+         */
+        public function getGlobals();
+
+        /**
+         * Returns the name of the extension.
+         *
+         * @return string The extension name
+         */
+        public function getName();
+    }
+
+To keep your extension class clean and lean, it can inherit from the built-in
+``Twig_Extension`` class instead of implementing the whole interface. That
+way, you just need to implement the ``getName()`` method as the
+``Twig_Extension`` provides empty implementations for all other methods.
+
+The ``getName()`` method must return a unique identifier for your extension.
+
+Now, with this information in mind, let's create the most basic extension
+possible::
+
+    class Project_Twig_Extension extends Twig_Extension
+    {
+        public function getName()
+        {
+            return 'project';
+        }
+    }
+
+.. note::
+
+    Of course, this extension does nothing for now. We will customize it in
+    the next sections.
+
+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());
+
+Of course, you need to first load the extension file by either using
+``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
+
+.. tip::
+
+    The bundled extensions are great examples of how extensions work.
+
+Globals and Functions
+---------------------
+
+Global variables and functions can be registered in an extensions via the
+``getGlobals()`` method::
+
+    class Project_Twig_Extension extends Twig_Extension
+    {
+        public function getGlobals()
+        {
+            return array(
+                'text' => new Text(),
+                'lipsum' => new Twig_Function(new Text(), 'getLipsum'),
+            );
+        }
+
+        // ...
+    }
+
+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()
+        {
+            return array(
+                'rot13' => new Twig_Filter_Function('str_rot13'),
+            );
+        }
+
+        // ...
+    }
+
+As you can see in the above code, the ``getFilters()`` method returns an array
+where keys are the name of the filters (``rot13``) and the values the
+definition of the filter (``new Twig_Filter_Function('str_rot13')``).
+
+As seen in the previous chapter, you can also define filters as static methods
+on the extension class::
+
+$twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'));
+
+You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function``
+when defining a filter to use a method::
+
+    class Project_Twig_Extension extends Twig_Extension
+    {
+        public function getFilters()
+        {
+            return array(
+                'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
+            );
+        }
+
+        public function rot13Filter($string)
+        {
+            return str_rot13($string);
+        }
+
+        // ...
+    }
+
+The first argument of the ``Twig_Filter_Method`` constructor is always
+``$this``, the current extension object. The second one is the name of the
+method to call.
+
+Using methods for filters is a great way to package your filter without
+polluting the global namespace. This also gives the developer more flexibility
+at the cost of a small overhead.
+
+Overriding default Filters
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If some default core filters do not suit your needs, you can easily override
+them by creating your own core extension. Of course, you don't need to copy
+and paste the whole core extension code of Twig. Instead, you can just extends
+it and override the filter(s) you want by overriding the ``getFilters()``
+method::
+
+    class MyCoreExtension extends Twig_Extension_Core
+    {
+        public function getFilters()
+        {
+            return array_merge(
+                parent::getFilters(),
+                array(
+                    'date' => Twig_Filter_Method($this, 'dateFilter')
+                )
+            );
+        }
+
+        public function dateFilter($timestamp, $format = 'F j, Y H:i')
+        {
+            return '...'.twig_date_format_filter($timestamp, $format);
+        }
+
+        // ...
+    }
+
+Here, we override the ``date`` filter with a custom one. Using this new core
+extension is as simple as registering the ``MyCoreExtension`` extension by
+calling the ``addExtension()`` method on the environment instance::
+
+    $twig = new Twig_Environment($loader);
+    $twig->addExtension(new MyCoreExtension());
+
+But I can already hear some people wondering how it can work as the Core
+extension is loaded by default. That's true, but the trick is that both
+extensions share the same unique identifier (``core`` - defined in the
+``getName()`` method). By registering an extension with the same name as an
+existing one, you have actually overridden the default one, even if it is
+already registered::
+
+    $twig->addExtension(new Twig_Extension_Core());
+    $twig->addExtension(new MyCoreExtension());
+
+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()
+        {
+            return array(new Project_Set_TokenParser());
+        }
+
+        // ...
+    }
+
+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
+---------
+
+The ``getOperators()`` methods allows to add new operators. Here is how to add
+``!``, ``||``, and ``&&`` operators::
+
+    class Project_Twig_Extension extends Twig_Extension
+    {
+        public function getOperators()
+        {
+            return array(
+                array(
+                    '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
+                ),
+                array(
+                    '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                    '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+                ),
+            );
+        }
+
+        // ...
+    }
+
+Tests
+-----
+
+The ``getTests()`` methods allows to add new test functions::
+
+    class Project_Twig_Extension extends Twig_Extension
+    {
+        public function getTests()
+        {
+            return array(
+                'even' => new Twig_Test_Function('twig_test_even'),
+            );
+        }
+
+        // ...
+    }
+
+.. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register