Yaffs site version 1.1
[yaffs-website] / vendor / twig / twig / doc / recipes.rst
diff --git a/vendor/twig/twig/doc/recipes.rst b/vendor/twig/twig/doc/recipes.rst
new file mode 100644 (file)
index 0000000..b3ba7f4
--- /dev/null
@@ -0,0 +1,568 @@
+Recipes
+=======
+
+.. _deprecation-notices:
+
+Displaying Deprecation Notices
+------------------------------
+
+.. versionadded:: 1.21
+    This works as of Twig 1.21.
+
+Deprecated features generate deprecation notices (via a call to the
+``trigger_error()`` PHP function). By default, they are silenced and never
+displayed nor logged.
+
+To easily remove all deprecated feature usages from your templates, write and
+run a script along the lines of the following::
+
+    require_once __DIR__.'/vendor/autoload.php';
+
+    $twig = create_your_twig_env();
+
+    $deprecations = new Twig_Util_DeprecationCollector($twig);
+
+    print_r($deprecations->collectDir(__DIR__.'/templates'));
+
+The ``collectDir()`` method compiles all templates found in a directory,
+catches deprecation notices, and return them.
+
+.. tip::
+
+    If your templates are not stored on the filesystem, use the ``collect()``
+    method instead. ``collect()`` takes a ``Traversable`` which must return
+    template names as keys and template contents as values (as done by
+    ``Twig_Util_TemplateDirIterator``).
+
+However, this code won't find all deprecations (like using deprecated some Twig
+classes). To catch all notices, register a custom error handler like the one
+below::
+
+    $deprecations = array();
+    set_error_handler(function ($type, $msg) use (&$deprecations) {
+        if (E_USER_DEPRECATED === $type) {
+            $deprecations[] = $msg;
+        }
+    });
+
+    // run your application
+
+    print_r($deprecations);
+
+Note that most deprecation notices are triggered during **compilation**, so
+they won't be generated when templates are already cached.
+
+.. tip::
+
+    If you want to manage the deprecation notices from your PHPUnit tests, have
+    a look at the `symfony/phpunit-bridge
+    <https://github.com/symfony/phpunit-bridge>`_ package, which eases the
+    process a lot.
+
+Making a Layout conditional
+---------------------------
+
+Working with Ajax means that the same content is sometimes displayed as is,
+and sometimes decorated with a layout. As Twig layout template names can be
+any valid expression, you can pass a variable that evaluates to ``true`` when
+the request is made via Ajax and choose the layout accordingly:
+
+.. code-block:: jinja
+
+    {% extends request.ajax ? "base_ajax.html" : "base.html" %}
+
+    {% block content %}
+        This is the content to be displayed.
+    {% endblock %}
+
+Making an Include dynamic
+-------------------------
+
+When including a template, its name does not need to be a string. For
+instance, the name can depend on the value of a variable:
+
+.. code-block:: jinja
+
+    {% include var ~ '_foo.html' %}
+
+If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
+rendered.
+
+As a matter of fact, the template name can be any valid expression, such as
+the following:
+
+.. code-block:: jinja
+
+    {% include var|default('index') ~ '_foo.html' %}
+
+Overriding a Template that also extends itself
+----------------------------------------------
+
+A template can be customized in two different ways:
+
+* *Inheritance*: A template *extends* a parent template and overrides some
+  blocks;
+
+* *Replacement*: If you use the filesystem loader, Twig loads the first
+  template it finds in a list of configured directories; a template found in a
+  directory *replaces* another one from a directory further in the list.
+
+But how do you combine both: *replace* a template that also extends itself
+(aka a template in a directory further in the list)?
+
+Let's say that your templates are loaded from both ``.../templates/mysite``
+and ``.../templates/default`` in this order. The ``page.twig`` template,
+stored in ``.../templates/default`` reads as follows:
+
+.. code-block:: jinja
+
+    {# page.twig #}
+    {% extends "layout.twig" %}
+
+    {% block content %}
+    {% endblock %}
+
+You can replace this template by putting a file with the same name in
+``.../templates/mysite``. And if you want to extend the original template, you
+might be tempted to write the following:
+
+.. code-block:: jinja
+
+    {# page.twig in .../templates/mysite #}
+    {% extends "page.twig" %} {# from .../templates/default #}
+
+Of course, this will not work as Twig will always load the template from
+``.../templates/mysite``.
+
+It turns out it is possible to get this to work, by adding a directory right
+at the end of your template directories, which is the parent of all of the
+other directories: ``.../templates`` in our case. This has the effect of
+making every template file within our system uniquely addressable. Most of the
+time you will use the "normal" paths, but in the special case of wanting to
+extend a template with an overriding version of itself we can reference its
+parent's full, unambiguous template path in the extends tag:
+
+.. code-block:: jinja
+
+    {# page.twig in .../templates/mysite #}
+    {% extends "default/page.twig" %} {# from .../templates #}
+
+.. note::
+
+    This recipe was inspired by the following Django wiki page:
+    http://code.djangoproject.com/wiki/ExtendingTemplates
+
+Customizing the Syntax
+----------------------
+
+Twig allows some syntax customization for the block delimiters. It's not
+recommended to use this feature as templates will be tied with your custom
+syntax. But for specific projects, it can make sense to change the defaults.
+
+To change the block delimiters, you need to create your own lexer object::
+
+    $twig = new Twig_Environment();
+
+    $lexer = new Twig_Lexer($twig, array(
+        'tag_comment'   => array('{#', '#}'),
+        'tag_block'     => array('{%', '%}'),
+        'tag_variable'  => array('{{', '}}'),
+        'interpolation' => array('#{', '}'),
+    ));
+    $twig->setLexer($lexer);
+
+Here are some configuration example that simulates some other template engines
+syntax::
+
+    // Ruby erb syntax
+    $lexer = new Twig_Lexer($twig, array(
+        'tag_comment'  => array('<%#', '%>'),
+        'tag_block'    => array('<%', '%>'),
+        'tag_variable' => array('<%=', '%>'),
+    ));
+
+    // SGML Comment Syntax
+    $lexer = new Twig_Lexer($twig, array(
+        'tag_comment'  => array('<!--#', '-->'),
+        'tag_block'    => array('<!--', '-->'),
+        'tag_variable' => array('${', '}'),
+    ));
+
+    // Smarty like
+    $lexer = new Twig_Lexer($twig, array(
+        'tag_comment'  => array('{*', '*}'),
+        'tag_block'    => array('{', '}'),
+        'tag_variable' => array('{$', '}'),
+    ));
+
+Using dynamic Object Properties
+-------------------------------
+
+When Twig encounters a variable like ``article.title``, it tries to find a
+``title`` public property in the ``article`` object.
+
+It also works if the property does not exist but is rather defined dynamically
+thanks to the magic ``__get()`` method; you just need to also implement the
+``__isset()`` magic method like shown in the following snippet of code::
+
+    class Article
+    {
+        public function __get($name)
+        {
+            if ('title' == $name) {
+                return 'The title';
+            }
+
+            // throw some kind of error
+        }
+
+        public function __isset($name)
+        {
+            if ('title' == $name) {
+                return true;
+            }
+
+            return false;
+        }
+    }
+
+Accessing the parent Context in Nested Loops
+--------------------------------------------
+
+Sometimes, when using nested loops, you need to access the parent context. The
+parent context is always accessible via the ``loop.parent`` variable. For
+instance, if you have the following template data::
+
+    $data = array(
+        'topics' => array(
+            'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
+            'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
+        ),
+    );
+
+And the following template to display all messages in all topics:
+
+.. code-block:: jinja
+
+    {% for topic, messages in topics %}
+        * {{ loop.index }}: {{ topic }}
+      {% for message in messages %}
+          - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
+      {% endfor %}
+    {% endfor %}
+
+The output will be similar to:
+
+.. code-block:: text
+
+    * 1: topic1
+      - 1.1: The message 1 of topic 1
+      - 1.2: The message 2 of topic 1
+    * 2: topic2
+      - 2.1: The message 1 of topic 2
+      - 2.2: The message 2 of topic 2
+
+In the inner loop, the ``loop.parent`` variable is used to access the outer
+context. So, the index of the current ``topic`` defined in the outer for loop
+is accessible via the ``loop.parent.loop.index`` variable.
+
+Defining undefined Functions and Filters on the Fly
+---------------------------------------------------
+
+When a function (or a filter) is not defined, Twig defaults to throw a
+``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
+valid PHP callable) which should return a function (or a filter).
+
+For filters, register callbacks with ``registerUndefinedFilterCallback()``.
+For functions, use ``registerUndefinedFunctionCallback()``::
+
+    // auto-register all native PHP functions as Twig functions
+    // don't try this at home as it's not secure at all!
+    $twig->registerUndefinedFunctionCallback(function ($name) {
+        if (function_exists($name)) {
+            return new Twig_SimpleFunction($name, $name);
+        }
+
+        return false;
+    });
+
+If the callable is not able to return a valid function (or filter), it must
+return ``false``.
+
+If you register more than one callback, Twig will call them in turn until one
+does not return ``false``.
+
+.. tip::
+
+    As the resolution of functions and filters is done during compilation,
+    there is no overhead when registering these callbacks.
+
+Validating the Template Syntax
+------------------------------
+
+When template code is provided by a third-party (through a web interface for
+instance), it might be interesting to validate the template syntax before
+saving it. If the template code is stored in a `$template` variable, here is
+how you can do it::
+
+    try {
+        $twig->parse($twig->tokenize(new Twig_Source($template)));
+
+        // the $template is valid
+    } catch (Twig_Error_Syntax $e) {
+        // $template contains one or more syntax errors
+    }
+
+If you iterate over a set of files, you can pass the filename to the
+``tokenize()`` method to get the filename in the exception message::
+
+    foreach ($files as $file) {
+        try {
+            $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
+
+            // the $template is valid
+        } catch (Twig_Error_Syntax $e) {
+            // $template contains one or more syntax errors
+        }
+    }
+
+.. versionadded:: 1.27
+    ``Twig_Source`` was introduced in version 1.27, pass the source and the
+    identifier directly on previous versions.
+
+.. note::
+
+    This method won't catch any sandbox policy violations because the policy
+    is enforced during template rendering (as Twig needs the context for some
+    checks like allowed methods on objects).
+
+Refreshing modified Templates when OPcache or APC is enabled
+------------------------------------------------------------
+
+When using OPcache with ``opcache.validate_timestamps`` set to ``0`` or APC
+with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing the template
+cache won't update the cache.
+
+To get around this, force Twig to invalidate the bytecode cache::
+
+    $twig = new Twig_Environment($loader, array(
+        'cache' => new Twig_Cache_Filesystem('/some/cache/path', Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION),
+        // ...
+    ));
+
+.. note::
+
+    Before Twig 1.22, you should extend ``Twig_Environment`` instead::
+
+        class OpCacheAwareTwigEnvironment extends Twig_Environment
+        {
+            protected function writeCacheFile($file, $content)
+            {
+                parent::writeCacheFile($file, $content);
+
+                // Compile cached file into bytecode cache
+                if (function_exists('opcache_invalidate')) {
+                    opcache_invalidate($file, true);
+                } elseif (function_exists('apc_compile_file')) {
+                    apc_compile_file($file);
+                }
+            }
+        }
+
+Reusing a stateful Node Visitor
+-------------------------------
+
+When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
+visit *all* templates it compiles. If you need to keep some state information
+around, you probably want to reset it when visiting a new template.
+
+This can be easily achieved with the following code::
+
+    protected $someTemplateState = array();
+
+    public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
+    {
+        if ($node instanceof Twig_Node_Module) {
+            // reset the state as we are entering a new template
+            $this->someTemplateState = array();
+        }
+
+        // ...
+
+        return $node;
+    }
+
+Using a Database to store Templates
+-----------------------------------
+
+If you are developing a CMS, templates are usually stored in a database. This
+recipe gives you a simple PDO template loader you can use as a starting point
+for your own.
+
+First, let's create a temporary in-memory SQLite3 database to work with::
+
+    $dbh = new PDO('sqlite::memory:');
+    $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
+    $base = '{% block content %}{% endblock %}';
+    $index = '
+    {% extends "base.twig" %}
+    {% block content %}Hello {{ name }}{% endblock %}
+    ';
+    $now = time();
+    $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
+    $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
+
+We have created a simple ``templates`` table that hosts two templates:
+``base.twig`` and ``index.twig``.
+
+Now, let's define a loader able to use this database::
+
+    class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
+    {
+        protected $dbh;
+
+        public function __construct(PDO $dbh)
+        {
+            $this->dbh = $dbh;
+        }
+
+        public function getSource($name)
+        {
+            if (false === $source = $this->getValue('source', $name)) {
+                throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
+            }
+
+            return $source;
+        }
+
+        // Twig_SourceContextLoaderInterface as of Twig 1.27
+        public function getSourceContext($name)
+        {
+            if (false === $source = $this->getValue('source', $name)) {
+                throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
+            }
+
+            return new Twig_Source($source, $name);
+        }
+
+        // Twig_ExistsLoaderInterface as of Twig 1.11
+        public function exists($name)
+        {
+            return $name === $this->getValue('name', $name);
+        }
+
+        public function getCacheKey($name)
+        {
+            return $name;
+        }
+
+        public function isFresh($name, $time)
+        {
+            if (false === $lastModified = $this->getValue('last_modified', $name)) {
+                return false;
+            }
+
+            return $lastModified <= $time;
+        }
+
+        protected function getValue($column, $name)
+        {
+            $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
+            $sth->execute(array(':name' => (string) $name));
+
+            return $sth->fetchColumn();
+        }
+    }
+
+Finally, here is an example on how you can use it::
+
+    $loader = new DatabaseTwigLoader($dbh);
+    $twig = new Twig_Environment($loader);
+
+    echo $twig->render('index.twig', array('name' => 'Fabien'));
+
+Using different Template Sources
+--------------------------------
+
+This recipe is the continuation of the previous one. Even if you store the
+contributed templates in a database, you might want to keep the original/base
+templates on the filesystem. When templates can be loaded from different
+sources, you need to use the ``Twig_Loader_Chain`` loader.
+
+As you can see in the previous recipe, we reference the template in the exact
+same way as we would have done it with a regular filesystem loader. This is
+the key to be able to mix and match templates coming from the database, the
+filesystem, or any other loader for that matter: the template name should be a
+logical name, and not the path from the filesystem::
+
+    $loader1 = new DatabaseTwigLoader($dbh);
+    $loader2 = new Twig_Loader_Array(array(
+        'base.twig' => '{% block content %}{% endblock %}',
+    ));
+    $loader = new Twig_Loader_Chain(array($loader1, $loader2));
+
+    $twig = new Twig_Environment($loader);
+
+    echo $twig->render('index.twig', array('name' => 'Fabien'));
+
+Now that the ``base.twig`` templates is defined in an array loader, you can
+remove it from the database, and everything else will still work as before.
+
+Loading a Template from a String
+--------------------------------
+
+From a template, you can easily load a template stored in a string via the
+``template_from_string`` function (available as of Twig 1.11 via the
+``Twig_Extension_StringLoader`` extension):
+
+.. code-block:: jinja
+
+    {{ include(template_from_string("Hello {{ name }}")) }}
+
+From PHP, it's also possible to load a template stored in a string via
+``Twig_Environment::createTemplate()`` (available as of Twig 1.18)::
+
+    $template = $twig->createTemplate('hello {{ name }}');
+    echo $template->render(array('name' => 'Fabien'));
+
+.. note::
+
+    Never use the ``Twig_Loader_String`` loader, which has severe limitations.
+
+Using Twig and AngularJS in the same Templates
+----------------------------------------------
+
+Mixing different template syntaxes in the same file is not a recommended
+practice as both AngularJS and Twig use the same delimiters in their syntax:
+``{{`` and ``}}``.
+
+Still, if you want to use AngularJS and Twig in the same template, there are
+two ways to make it work depending on the amount of AngularJS you need to
+include in your templates:
+
+* Escaping the AngularJS delimiters by wrapping AngularJS sections with the
+  ``{% verbatim %}`` tag or by escaping each delimiter via ``{{ '{{' }}`` and
+  ``{{ '}}' }}``;
+
+* Changing the delimiters of one of the template engines (depending on which
+  engine you introduced last):
+
+  * For AngularJS, change the interpolation tags using the
+    ``interpolateProvider`` service, for instance at the module initialization
+    time:
+
+    ..  code-block:: javascript
+
+        angular.module('myApp', []).config(function($interpolateProvider) {
+            $interpolateProvider.startSymbol('{[').endSymbol(']}');
+        });
+
+  * For Twig, change the delimiters via the ``tag_variable`` Lexer option:
+
+    ..  code-block:: php
+
+        $env->setLexer(new Twig_Lexer($env, array(
+            'tag_variable' => array('{[', ']}'),
+        )));
+
+.. _callback: http://www.php.net/manual/en/function.is-callable.php