123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- 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 which takes an ``Iterator``; the iterator 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_Function_Function($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($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($template, $file));
-
- // the $template is valid
- } catch (Twig_Error_Syntax $e) {
- // $template contains one or more syntax errors
- }
- }
-
- .. 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
- {
- 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_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.
-
- .. _callback: http://www.php.net/manual/en/function.is-callable.php
|