Update Twig (now uses composer) and add composer.json.
This commit is contained in:
parent
070cb92661
commit
7d2f97f2f6
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,2 +1,5 @@
|
|||||||
pico.sublime-project
|
composer.lock
|
||||||
pico.sublime-workspace
|
composer.phar
|
||||||
|
vendor/twig/twig/doc/*
|
||||||
|
vendor/twig/twig/ext/*
|
||||||
|
vendor/twig/twig/test/*
|
5
composer.json
Normal file
5
composer.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"twig/twig": "1.12.*"
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
/*
|
/*
|
||||||
Override any of the default settings below:
|
Override any of the default settings below:
|
||||||
|
|
||||||
$config['site_title'] = 'Pico'; // Site title
|
$config['site_title'] = 'Pico'; // Site title
|
||||||
$config['base_url'] = ''; // Override base URL (e.g. http://example.com)
|
$config['base_url'] = ''; // Override base URL (e.g. http://example.com)
|
||||||
$config['theme'] = 'default'; // Set the theme (defaults to "default")
|
$config['theme'] = 'default'; // Set the theme (defaults to "default")
|
||||||
$config['enable_cache'] = false; // Enable caching
|
$config['enable_cache'] = false; // Enable caching
|
||||||
|
@ -11,8 +11,8 @@ define('THEMES_DIR', ROOT_DIR .'themes/');
|
|||||||
define('CACHE_DIR', LIB_DIR .'cache/');
|
define('CACHE_DIR', LIB_DIR .'cache/');
|
||||||
|
|
||||||
require('config.php');
|
require('config.php');
|
||||||
|
require(ROOT_DIR .'vendor/autoload.php');
|
||||||
require(LIB_DIR .'markdown.php');
|
require(LIB_DIR .'markdown.php');
|
||||||
require(LIB_DIR .'twig/lib/Twig/Autoloader.php');
|
|
||||||
require(LIB_DIR .'pico.php');
|
require(LIB_DIR .'pico.php');
|
||||||
$pico = new Pico();
|
$pico = new Pico();
|
||||||
|
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
if (!isset($argv[1]))
|
|
||||||
{
|
|
||||||
die('You must provide the version (1.0.0)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($argv[2]))
|
|
||||||
{
|
|
||||||
die('You must provide the stability (alpha, beta, or stable)');
|
|
||||||
}
|
|
||||||
|
|
||||||
$context = array(
|
|
||||||
'date' => date('Y-m-d'),
|
|
||||||
'time' => date('H:m:00'),
|
|
||||||
'version' => $argv[1],
|
|
||||||
'api_version' => $argv[1],
|
|
||||||
'stability' => $argv[2],
|
|
||||||
'api_stability' => $argv[2],
|
|
||||||
);
|
|
||||||
|
|
||||||
$context['files'] = '';
|
|
||||||
$path = realpath(dirname(__FILE__).'/../lib/Twig');
|
|
||||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
|
|
||||||
{
|
|
||||||
if (preg_match('/\.php$/', $file))
|
|
||||||
{
|
|
||||||
$name = str_replace($path.'/', '', $file);
|
|
||||||
$context['files'] .= ' <file install-as="Twig/'.$name.'" name="'.$name.'" role="php" />'."\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$template = file_get_contents(dirname(__FILE__).'/../package.xml.tpl');
|
|
||||||
$content = preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', 'replace_parameters', $template);
|
|
||||||
file_put_contents(dirname(__FILE__).'/../package.xml', $content);
|
|
||||||
|
|
||||||
function replace_parameters($matches)
|
|
||||||
{
|
|
||||||
global $context;
|
|
||||||
|
|
||||||
return isset($context[$matches[1]]) ? $context[$matches[1]] : null;
|
|
||||||
}
|
|
@ -1,489 +0,0 @@
|
|||||||
Extending 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::
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Before extending Twig, you must understand the differences between all the
|
|
||||||
different possible extension points and when to use them.
|
|
||||||
|
|
||||||
First, remember that Twig has two main language constructs:
|
|
||||||
|
|
||||||
* ``{{ }}``: used to print the result of an expression evaluation;
|
|
||||||
|
|
||||||
* ``{% %}``: used to execute statements.
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
You can use a ``lipsum`` *tag*:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% lipsum 40 %}
|
|
||||||
|
|
||||||
That works, but using a tag for ``lipsum`` is not a good idea for at least
|
|
||||||
three main reasons:
|
|
||||||
|
|
||||||
* ``lipsum`` is not a language construct;
|
|
||||||
* The tag outputs something;
|
|
||||||
* The tag is not flexible as you cannot use it in an expression:
|
|
||||||
|
|
||||||
.. 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.
|
|
||||||
|
|
||||||
Keep in mind the following when you want to extend Twig:
|
|
||||||
|
|
||||||
========== ========================== ========== =========================
|
|
||||||
What? Implementation difficulty? How often? When?
|
|
||||||
========== ========================== ========== =========================
|
|
||||||
*macro* trivial frequent Content generation
|
|
||||||
*global* trivial frequent Helper object
|
|
||||||
*function* trivial frequent Content generation
|
|
||||||
*filter* trivial frequent Value transformation
|
|
||||||
*tag* complex rare DSL language construct
|
|
||||||
*test* trivial rare Boolean decision
|
|
||||||
*operator* trivial rare Values transformation
|
|
||||||
========== ========================== ========== =========================
|
|
||||||
|
|
||||||
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->addGlobal('text', new Text());
|
|
||||||
|
|
||||||
You can then use the ``text`` variable anywhere in a template:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ text.lipsum(40) }}
|
|
||||||
|
|
||||||
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 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:
|
|
||||||
|
|
||||||
.. code-block:: html+php
|
|
||||||
|
|
||||||
<?php echo strtolower('TWIG') ?>
|
|
||||||
|
|
||||||
As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP
|
|
||||||
function.
|
|
||||||
|
|
||||||
A filter can also take extra arguments like in the following example:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 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:
|
|
||||||
|
|
||||||
.. code-block:: html+php
|
|
||||||
|
|
||||||
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>
|
|
||||||
|
|
||||||
Let's see how to create a new filter.
|
|
||||||
|
|
||||||
In this section, we will create a ``rot13`` filter, which should return the
|
|
||||||
`rot13`_ transformation of a string. Here is an example of its usage and the
|
|
||||||
expected output:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "Twig"|rot13 }}
|
|
||||||
|
|
||||||
{# should displays Gjvt #}
|
|
||||||
|
|
||||||
Adding a filter is as simple as calling the ``addFilter()`` method on the
|
|
||||||
``Twig_Environment`` instance::
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->addFilter('rot13', new Twig_Filter_Function('str_rot13'));
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "Twig"|rot13('prefix_') }}
|
|
||||||
|
|
||||||
{# should displays prefix_Gjvt #}
|
|
||||||
|
|
||||||
As the PHP ``str_rot13()`` function does not support this requirement, let's
|
|
||||||
create a new PHP function::
|
|
||||||
|
|
||||||
function project_compute_rot13($string, $prefix = '')
|
|
||||||
{
|
|
||||||
return $prefix.str_rot13($string);
|
|
||||||
}
|
|
||||||
|
|
||||||
As you can see, the ``prefix`` argument of the filter is passed as an extra
|
|
||||||
argument to the ``project_compute_rot13()`` function.
|
|
||||||
|
|
||||||
Adding this filter is as easy as before::
|
|
||||||
|
|
||||||
$twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13'));
|
|
||||||
|
|
||||||
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::
|
|
||||||
|
|
||||||
$twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter'));
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
In an extension, you can also define a filter as a static method of the
|
|
||||||
extension class.
|
|
||||||
|
|
||||||
Environment aware Filters
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``Twig_Filter`` classes take options as their last argument. For instance,
|
|
||||||
if you want access to the current environment instance in your filter, set the
|
|
||||||
``needs_environment`` option to ``true``::
|
|
||||||
|
|
||||||
$filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true));
|
|
||||||
|
|
||||||
Twig will then pass the current environment as the first argument to the
|
|
||||||
filter call::
|
|
||||||
|
|
||||||
function twig_compute_rot13(Twig_Environment $env, $string)
|
|
||||||
{
|
|
||||||
// get the current charset for instance
|
|
||||||
$charset = $env->getCharset();
|
|
||||||
|
|
||||||
return str_rot13($string);
|
|
||||||
}
|
|
||||||
|
|
||||||
Automatic Escaping
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If automatic escaping is enabled, the output of the filter may be escaped
|
|
||||||
before printing. If your filter acts as an escaper (or explicitly outputs html
|
|
||||||
or javascript code), you will want the raw output to be printed. In such a
|
|
||||||
case, set the ``is_safe`` option::
|
|
||||||
|
|
||||||
$filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
|
|
||||||
|
|
||||||
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')));
|
|
||||||
|
|
||||||
Dynamic Filters
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
Dynamic filters support was added in Twig 1.5.
|
|
||||||
|
|
||||||
A filter name containing the special ``*`` character is a dynamic filter as
|
|
||||||
the ``*`` can be any string::
|
|
||||||
|
|
||||||
$twig->addFilter('*_path', new Twig_Filter_Function('twig_path'));
|
|
||||||
|
|
||||||
function twig_path($name, $arguments)
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
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::
|
|
||||||
|
|
||||||
$twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
|
|
||||||
|
|
||||||
function twig_path($name, $suffix, $arguments)
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
The filter will receive all dynamic part values before the normal filters
|
|
||||||
arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the
|
|
||||||
following PHP call: ``twig_path('a', 'b', 'foo')``.
|
|
||||||
|
|
||||||
Functions
|
|
||||||
---------
|
|
||||||
|
|
||||||
A function is a regular PHP function or an object method that can be called from
|
|
||||||
templates.
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ constant("DATE_W3C") }}
|
|
||||||
|
|
||||||
When compiling this template to PHP, Twig looks for the PHP callable
|
|
||||||
associated with the ``constant`` function. The ``constant`` function is a built-in Twig
|
|
||||||
function, and it is simply mapped to the PHP ``constant()`` function. After
|
|
||||||
compilation, the generated PHP code is roughly equivalent to:
|
|
||||||
|
|
||||||
.. code-block:: html+php
|
|
||||||
|
|
||||||
<?php echo constant('DATE_W3C') ?>
|
|
||||||
|
|
||||||
Adding a function is similar to adding a filter. This can be done by calling the
|
|
||||||
``addFunction()`` method on the ``Twig_Environment`` instance::
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->addFunction('functionName', new Twig_Function_Function('someFunction'));
|
|
||||||
|
|
||||||
You can also expose extension methods as functions in your templates::
|
|
||||||
|
|
||||||
// $this is an object that implements Twig_ExtensionInterface.
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod'));
|
|
||||||
|
|
||||||
Functions also support ``needs_environment`` and ``is_safe`` parameters.
|
|
||||||
|
|
||||||
Dynamic Functions
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
Dynamic functions support was added in Twig 1.5.
|
|
||||||
|
|
||||||
A function name containing the special ``*`` character is a dynamic function
|
|
||||||
as the ``*`` can be any string::
|
|
||||||
|
|
||||||
$twig->addFunction('*_path', new Twig_Function_Function('twig_path'));
|
|
||||||
|
|
||||||
function twig_path($name, $arguments)
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
The following functions will be matched by the above defined dynamic function:
|
|
||||||
|
|
||||||
* ``product_path``
|
|
||||||
* ``category_path``
|
|
||||||
|
|
||||||
A dynamic function can define more than one dynamic parts::
|
|
||||||
|
|
||||||
$twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
|
|
||||||
|
|
||||||
function twig_path($name, $suffix, $arguments)
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
The function will receive all dynamic part values before the normal functions
|
|
||||||
arguments. For instance, a call to ``a_path_b('foo')`` will result in the
|
|
||||||
following PHP call: ``twig_path('a', 'b', 'foo')``.
|
|
||||||
|
|
||||||
Tags
|
|
||||||
----
|
|
||||||
|
|
||||||
One of the most exciting feature of a template engine like Twig is the
|
|
||||||
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:
|
|
||||||
|
|
||||||
.. 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);
|
|
||||||
|
|
||||||
* Registering the tag.
|
|
||||||
|
|
||||||
Registering a new tag
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Adding a tag is as simple as calling the ``addTokenParser`` method on the
|
|
||||||
``Twig_Environment`` instance::
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->addTokenParser(new Project_Set_TokenParser());
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
$lineno = $token->getLine();
|
|
||||||
$name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
|
|
||||||
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '=');
|
|
||||||
$value = $this->parser->getExpressionParser()->parseExpression();
|
|
||||||
|
|
||||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
|
||||||
|
|
||||||
return new Project_Set_Node($name, $value, $lineno, $this->getTag());
|
|
||||||
}
|
|
||||||
|
|
||||||
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()``):
|
|
||||||
|
|
||||||
* ``getCurrent()``: Gets the current token in the stream.
|
|
||||||
|
|
||||||
* ``next()``: Moves to the next token in the stream, *but returns the old one*.
|
|
||||||
|
|
||||||
* ``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.
|
|
||||||
|
|
||||||
* ``look()``: Looks a the next token without consuming it.
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null)
|
|
||||||
{
|
|
||||||
parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function compile(Twig_Compiler $compiler)
|
|
||||||
{
|
|
||||||
$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).
|
|
||||||
|
|
||||||
.. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
|
|
@ -1,479 +0,0 @@
|
|||||||
Twig for Developers
|
|
||||||
===================
|
|
||||||
|
|
||||||
This chapter describes the API to Twig and not the template language. It will
|
|
||||||
be most useful as reference to those implementing the template interface to
|
|
||||||
the application and not those who are creating Twig templates.
|
|
||||||
|
|
||||||
Basics
|
|
||||||
------
|
|
||||||
|
|
||||||
Twig uses a central object called the **environment** (of class
|
|
||||||
``Twig_Environment``). Instances of this class are used to store the
|
|
||||||
configuration and extensions, and are used to load templates from the file
|
|
||||||
system or other locations.
|
|
||||||
|
|
||||||
Most applications will create one ``Twig_Environment`` object on application
|
|
||||||
initialization and use that to load templates. In some cases it's however
|
|
||||||
useful to have multiple environments side by side, if different configurations
|
|
||||||
are in use.
|
|
||||||
|
|
||||||
The simplest way to configure Twig to load templates for your application
|
|
||||||
looks roughly like this::
|
|
||||||
|
|
||||||
require_once '/path/to/lib/Twig/Autoloader.php';
|
|
||||||
Twig_Autoloader::register();
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_Filesystem('/path/to/templates');
|
|
||||||
$twig = new Twig_Environment($loader, array(
|
|
||||||
'cache' => '/path/to/compilation_cache',
|
|
||||||
));
|
|
||||||
|
|
||||||
This will create a template environment with the default settings and a loader
|
|
||||||
that looks up the templates in the ``/path/to/templates/`` folder. Different
|
|
||||||
loaders are available and you can also write your own if you want to load
|
|
||||||
templates from a database or other resources.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Notice that the second argument of the environment is an array of options.
|
|
||||||
The ``cache`` option is a compilation cache directory, where Twig caches
|
|
||||||
the compiled templates to avoid the parsing phase for sub-sequent
|
|
||||||
requests. It is very different from the cache you might want to add for
|
|
||||||
the evaluated templates. For such a need, you can use any available PHP
|
|
||||||
cache library.
|
|
||||||
|
|
||||||
To load a template from this environment you just have to call the
|
|
||||||
``loadTemplate()`` method which then returns a ``Twig_Template`` instance::
|
|
||||||
|
|
||||||
$template = $twig->loadTemplate('index.html');
|
|
||||||
|
|
||||||
To render the template with some variables, call the ``render()`` method::
|
|
||||||
|
|
||||||
echo $template->render(array('the' => 'variables', 'go' => 'here'));
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The ``display()`` method is a shortcut to output the template directly.
|
|
||||||
|
|
||||||
You can also load and render the template in one fell swoop::
|
|
||||||
|
|
||||||
echo $twig->render('index.html', array('the' => 'variables', 'go' => 'here'));
|
|
||||||
|
|
||||||
Environment Options
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
When creating a new ``Twig_Environment`` instance, you can pass an array of
|
|
||||||
options as the constructor second argument::
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader, array('debug' => true));
|
|
||||||
|
|
||||||
The following options are available:
|
|
||||||
|
|
||||||
* ``debug``: When set to ``true``, the generated templates have a
|
|
||||||
``__toString()`` method that you can use to display the generated nodes
|
|
||||||
(default to ``false``).
|
|
||||||
|
|
||||||
* ``charset``: The charset used by the templates (default to ``utf-8``).
|
|
||||||
|
|
||||||
* ``base_template_class``: The base template class to use for generated
|
|
||||||
templates (default to ``Twig_Template``).
|
|
||||||
|
|
||||||
* ``cache``: An absolute path where to store the compiled templates, or
|
|
||||||
``false`` to disable caching (which is the default).
|
|
||||||
|
|
||||||
* ``auto_reload``: When developing with Twig, it's useful to recompile the
|
|
||||||
template whenever the source code changes. If you don't provide a value for
|
|
||||||
the ``auto_reload`` option, it will be determined automatically based on the
|
|
||||||
``debug`` value.
|
|
||||||
|
|
||||||
* ``strict_variables``: If set to ``false``, Twig will silently ignore invalid
|
|
||||||
variables (variables and or attributes/methods that do not exist) and
|
|
||||||
replace them with a ``null`` value. When set to ``true``, Twig throws an
|
|
||||||
exception instead (default to ``false``).
|
|
||||||
|
|
||||||
* ``autoescape``: If set to ``true``, auto-escaping will be enabled by default
|
|
||||||
for all templates (default to ``true``).
|
|
||||||
|
|
||||||
* ``optimizations``: A flag that indicates which optimizations to apply
|
|
||||||
(default to ``-1`` -- all optimizations are enabled; set it to ``0`` to
|
|
||||||
disable).
|
|
||||||
|
|
||||||
Loaders
|
|
||||||
-------
|
|
||||||
|
|
||||||
Loaders are responsible for loading templates from a resource such as the file
|
|
||||||
system.
|
|
||||||
|
|
||||||
Compilation Cache
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
All template loaders can cache the compiled templates on the filesystem for
|
|
||||||
future reuse. It speeds up Twig a lot as templates are only compiled once; and
|
|
||||||
the performance boost is even larger if you use a PHP accelerator such as APC.
|
|
||||||
See the ``cache`` and ``auto_reload`` options of ``Twig_Environment`` above
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
Built-in Loaders
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Here is a list of the built-in loaders Twig provides:
|
|
||||||
|
|
||||||
* ``Twig_Loader_Filesystem``: Loads templates from the file system. This
|
|
||||||
loader can find templates in folders on the file system and is the preferred
|
|
||||||
way to load them::
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_Filesystem($templateDir);
|
|
||||||
|
|
||||||
It can also look for templates in an array of directories::
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2));
|
|
||||||
|
|
||||||
With such a configuration, Twig will first look for templates in
|
|
||||||
``$templateDir1`` and if they do not exist, it will fallback to look for
|
|
||||||
them in the ``$templateDir2``.
|
|
||||||
|
|
||||||
* ``Twig_Loader_String``: Loads templates from a string. It's a dummy loader
|
|
||||||
as you pass it the source code directly::
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_String();
|
|
||||||
|
|
||||||
* ``Twig_Loader_Array``: Loads a template from a PHP array. It's passed an
|
|
||||||
array of strings bound to template names. This loader is useful for unit
|
|
||||||
testing::
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_Array($templates);
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
When using the ``Array`` or ``String`` loaders with a cache mechanism, you
|
|
||||||
should know that a new cache key is generated each time a template content
|
|
||||||
"changes" (the cache key being the source code of the template). If you
|
|
||||||
don't want to see your cache grows out of control, you need to take care
|
|
||||||
of clearing the old cache file by yourself.
|
|
||||||
|
|
||||||
Create your own Loader
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
All loaders implement the ``Twig_LoaderInterface``::
|
|
||||||
|
|
||||||
interface Twig_LoaderInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Gets the source code of a template, given its name.
|
|
||||||
*
|
|
||||||
* @param string $name string The name of the template to load
|
|
||||||
*
|
|
||||||
* @return string The template source code
|
|
||||||
*/
|
|
||||||
function getSource($name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the cache key to use for the cache for a given template name.
|
|
||||||
*
|
|
||||||
* @param string $name string The name of the template to load
|
|
||||||
*
|
|
||||||
* @return string The cache key
|
|
||||||
*/
|
|
||||||
function getCacheKey($name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the template is still fresh.
|
|
||||||
*
|
|
||||||
* @param string $name The template name
|
|
||||||
* @param timestamp $time The last modification time of the cached template
|
|
||||||
*/
|
|
||||||
function isFresh($name, $time);
|
|
||||||
}
|
|
||||||
|
|
||||||
As an example, here is how the built-in ``Twig_Loader_String`` reads::
|
|
||||||
|
|
||||||
class Twig_Loader_String implements Twig_LoaderInterface
|
|
||||||
{
|
|
||||||
public function getSource($name)
|
|
||||||
{
|
|
||||||
return $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCacheKey($name)
|
|
||||||
{
|
|
||||||
return $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isFresh($name, $time)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
The ``isFresh()`` method must return ``true`` if the current cached template
|
|
||||||
is still fresh, given the last modification time, or ``false`` otherwise.
|
|
||||||
|
|
||||||
Using Extensions
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Twig extensions are packages that add new features to Twig. Using an
|
|
||||||
extension is as simple as using the ``addExtension()`` method::
|
|
||||||
|
|
||||||
$twig->addExtension(new Twig_Extension_Sandbox());
|
|
||||||
|
|
||||||
Twig comes bundled with the following extensions:
|
|
||||||
|
|
||||||
* *Twig_Extension_Core*: Defines all the core features of Twig.
|
|
||||||
|
|
||||||
* *Twig_Extension_Escaper*: Adds automatic output-escaping and the possibility
|
|
||||||
to escape/unescape blocks of code.
|
|
||||||
|
|
||||||
* *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig
|
|
||||||
environment, making it safe to evaluated untrusted code.
|
|
||||||
|
|
||||||
* *Twig_Extension_Optimizer*: Optimizers the node tree before compilation.
|
|
||||||
|
|
||||||
The core, escaper, and optimizer extensions do not need to be added to the
|
|
||||||
Twig environment, as they are registered by default. You can disable an
|
|
||||||
already registered extension::
|
|
||||||
|
|
||||||
$twig->removeExtension('escaper');
|
|
||||||
|
|
||||||
Built-in Extensions
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
This section describes the features added by the built-in extensions.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
Read the chapter about extending Twig to learn how to create your own
|
|
||||||
extensions.
|
|
||||||
|
|
||||||
Core Extension
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``core`` extension defines all the core features of Twig:
|
|
||||||
|
|
||||||
* Tags:
|
|
||||||
|
|
||||||
* ``for``
|
|
||||||
* ``if``
|
|
||||||
* ``extends``
|
|
||||||
* ``include``
|
|
||||||
* ``block``
|
|
||||||
* ``filter``
|
|
||||||
* ``macro``
|
|
||||||
* ``import``
|
|
||||||
* ``from``
|
|
||||||
* ``set``
|
|
||||||
* ``spaceless``
|
|
||||||
|
|
||||||
* Filters:
|
|
||||||
|
|
||||||
* ``date``
|
|
||||||
* ``format``
|
|
||||||
* ``replace``
|
|
||||||
* ``url_encode``
|
|
||||||
* ``json_encode``
|
|
||||||
* ``title``
|
|
||||||
* ``capitalize``
|
|
||||||
* ``upper``
|
|
||||||
* ``lower``
|
|
||||||
* ``striptags``
|
|
||||||
* ``join``
|
|
||||||
* ``reverse``
|
|
||||||
* ``length``
|
|
||||||
* ``sort``
|
|
||||||
* ``merge``
|
|
||||||
* ``default``
|
|
||||||
* ``keys``
|
|
||||||
* ``escape``
|
|
||||||
* ``e``
|
|
||||||
|
|
||||||
* Functions:
|
|
||||||
|
|
||||||
* ``range``
|
|
||||||
* ``constant``
|
|
||||||
* ``cycle``
|
|
||||||
* ``parent``
|
|
||||||
* ``block``
|
|
||||||
|
|
||||||
* Tests:
|
|
||||||
|
|
||||||
* ``even``
|
|
||||||
* ``odd``
|
|
||||||
* ``defined``
|
|
||||||
* ``sameas``
|
|
||||||
* ``null``
|
|
||||||
* ``divisibleby``
|
|
||||||
* ``constant``
|
|
||||||
* ``empty``
|
|
||||||
|
|
||||||
Escaper Extension
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``escaper`` extension adds automatic output escaping to Twig. It defines a
|
|
||||||
new tag, ``autoescape``, and a new filter, ``raw``.
|
|
||||||
|
|
||||||
When creating the escaper extension, you can switch on or off the global
|
|
||||||
output escaping strategy::
|
|
||||||
|
|
||||||
$escaper = new Twig_Extension_Escaper(true);
|
|
||||||
$twig->addExtension($escaper);
|
|
||||||
|
|
||||||
If set to ``true``, all variables in templates are escaped, except those using
|
|
||||||
the ``raw`` filter:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ article.to_html|raw }}
|
|
||||||
|
|
||||||
You can also change the escaping mode locally by using the ``autoescape`` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% autoescape true %}
|
|
||||||
{{ var }}
|
|
||||||
{{ var|raw }} {# var won't be escaped #}
|
|
||||||
{{ var|escape }} {# var won't be double-escaped #}
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
The ``autoescape`` tag has no effect on included files.
|
|
||||||
|
|
||||||
The escaping rules are implemented as follows:
|
|
||||||
|
|
||||||
* Literals (integers, booleans, arrays, ...) used in the template directly as
|
|
||||||
variables or filter arguments are never automatically escaped:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "Twig<br />" }} {# won't be escaped #}
|
|
||||||
|
|
||||||
{% set text = "Twig<br />" %}
|
|
||||||
{{ text }} {# will be escaped #}
|
|
||||||
|
|
||||||
* Expressions which the result is always a literal or a variable marked safe
|
|
||||||
are never automatically escaped:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ foo ? "Twig<br />" : "<br />Twig" }} {# won't be escaped #}
|
|
||||||
|
|
||||||
{% set text = "Twig<br />" %}
|
|
||||||
{{ foo ? text : "<br />Twig" }} {# will be escaped #}
|
|
||||||
|
|
||||||
{% set text = "Twig<br />" %}
|
|
||||||
{{ foo ? text|raw : "<br />Twig" }} {# won't be escaped #}
|
|
||||||
|
|
||||||
{% set text = "Twig<br />" %}
|
|
||||||
{{ foo ? text|escape : "<br />Twig" }} {# the result of the expression won't be escaped #}
|
|
||||||
|
|
||||||
* Escaping is applied before printing, after any other filter is applied:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var|upper }} {# is equivalent to {{ var|upper|escape }} #}
|
|
||||||
|
|
||||||
* The `raw` filter should only be used at the end of the filter chain:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var|raw|upper }} {# will be escaped #}
|
|
||||||
|
|
||||||
{{ var|upper|raw }} {# won't be escaped #}
|
|
||||||
|
|
||||||
* Automatic escaping is not applied if the last filter in the chain is marked
|
|
||||||
safe for the current context (e.g. ``html`` or ``js``). ``escaper`` and
|
|
||||||
``escaper('html')`` are marked safe for html, ``escaper('js')`` is marked
|
|
||||||
safe for javascript, ``raw`` is marked safe for everything.
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% autoescape true js %}
|
|
||||||
{{ var|escape('html') }} {# will be escaped for html and javascript #}
|
|
||||||
{{ var }} {# will be escaped for javascript #}
|
|
||||||
{{ var|escape('js') }} {# won't be double-escaped #}
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Note that autoescaping has some limitations as escaping is applied on
|
|
||||||
expressions after evaluation. For instance, when working with
|
|
||||||
concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as
|
|
||||||
escaping is applied on the result of the concatenation, not on the
|
|
||||||
individual variables (so, the ``raw`` filter won't have any effect here).
|
|
||||||
|
|
||||||
Sandbox Extension
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``sandbox`` extension can be used to evaluate untrusted code. Access to
|
|
||||||
unsafe attributes and methods is prohibited. The sandbox security is managed
|
|
||||||
by a policy instance. By default, Twig comes with one policy class:
|
|
||||||
``Twig_Sandbox_SecurityPolicy``. This class allows you to white-list some
|
|
||||||
tags, filters, properties, and methods::
|
|
||||||
|
|
||||||
$tags = array('if');
|
|
||||||
$filters = array('upper');
|
|
||||||
$methods = array(
|
|
||||||
'Article' => array('getTitle', 'getBody'),
|
|
||||||
);
|
|
||||||
$properties = array(
|
|
||||||
'Article' => array('title', 'body'),
|
|
||||||
);
|
|
||||||
$functions = array('range');
|
|
||||||
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
|
|
||||||
|
|
||||||
With the previous configuration, the security policy will only allow usage of
|
|
||||||
the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be
|
|
||||||
able to call the ``getTitle()`` and ``getBody()`` methods on ``Article``
|
|
||||||
objects, and the ``title`` and ``body`` public properties. Everything else
|
|
||||||
won't be allowed and will generate a ``Twig_Sandbox_SecurityError`` exception.
|
|
||||||
|
|
||||||
The policy object is the first argument of the sandbox constructor::
|
|
||||||
|
|
||||||
$sandbox = new Twig_Extension_Sandbox($policy);
|
|
||||||
$twig->addExtension($sandbox);
|
|
||||||
|
|
||||||
By default, the sandbox mode is disabled and should be enabled when including
|
|
||||||
untrusted template code by using the ``sandbox`` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% sandbox %}
|
|
||||||
{% include 'user.html' %}
|
|
||||||
{% endsandbox %}
|
|
||||||
|
|
||||||
You can sandbox all templates by passing ``true`` as the second argument of
|
|
||||||
the extension constructor::
|
|
||||||
|
|
||||||
$sandbox = new Twig_Extension_Sandbox($policy, true);
|
|
||||||
|
|
||||||
Optimizer Extension
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``optimizer`` extension optimizes the node tree before compilation::
|
|
||||||
|
|
||||||
$twig->addExtension(new Twig_Extension_Optimizer());
|
|
||||||
|
|
||||||
By default, all optimizations are turned on. You can select the ones you want
|
|
||||||
to enable by passing them to the constructor::
|
|
||||||
|
|
||||||
$optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR);
|
|
||||||
|
|
||||||
$twig->addExtension($optimizer);
|
|
||||||
|
|
||||||
Exceptions
|
|
||||||
----------
|
|
||||||
|
|
||||||
Twig can throw exceptions:
|
|
||||||
|
|
||||||
* ``Twig_Error``: The base exception for all errors.
|
|
||||||
|
|
||||||
* ``Twig_Error_Syntax``: Thrown to tell the user that there is a problem with
|
|
||||||
the template syntax.
|
|
||||||
|
|
||||||
* ``Twig_Error_Runtime``: Thrown when an error occurs at runtime (when a filter
|
|
||||||
does not exist for instance).
|
|
||||||
|
|
||||||
* ``Twig_Error_Loader``: Thrown when an error occurs during template loading.
|
|
||||||
|
|
||||||
* ``Twig_Sandbox_SecurityError``: Thrown when an unallowed tag, filter, or
|
|
||||||
method is called in a sandboxed template.
|
|
@ -1,101 +0,0 @@
|
|||||||
Coding Standards
|
|
||||||
================
|
|
||||||
|
|
||||||
When writing Twig templates, we recommend you to follow these official coding
|
|
||||||
standards:
|
|
||||||
|
|
||||||
* Put one (and only one) space after the start of a delimiter (``{{``, ``{%``,
|
|
||||||
and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ foo }}
|
|
||||||
{# comment #}
|
|
||||||
{% if foo %}{% endif %}
|
|
||||||
|
|
||||||
When using the whitespace control character, do not put any spaces between
|
|
||||||
it and the delimiter:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{- foo -}}
|
|
||||||
{#- comment -#}
|
|
||||||
{%- if foo -%}{%- endif -%}
|
|
||||||
|
|
||||||
* Put one (and only one) space before and after the following operators:
|
|
||||||
comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math
|
|
||||||
operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic
|
|
||||||
operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary
|
|
||||||
operator (``?:``):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 1 + 2 }}
|
|
||||||
{{ foo ~ bar }}
|
|
||||||
{{ true ? true : false }}
|
|
||||||
|
|
||||||
* Put one (and only one) space after the ``:`` sign in hashes and ``,`` in
|
|
||||||
arrays and hashes:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ [1, 2, 3] }}
|
|
||||||
{{ {'foo': 'bar'} }}
|
|
||||||
|
|
||||||
* Do not put any spaces after an opening parenthesis and before a closing
|
|
||||||
parenthesis in expressions:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 1 + (2 * 3) }}
|
|
||||||
|
|
||||||
* Do not put any spaces before and after string delimiters:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 'foo' }}
|
|
||||||
{{ "foo" }}
|
|
||||||
|
|
||||||
* Do not put any spaces before and after the following operators: ``|``,
|
|
||||||
``.``, ``..``, ``[]``:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ foo|upper|lower }}
|
|
||||||
{{ user.name }}
|
|
||||||
{{ user[name] }}
|
|
||||||
{% for i in 1..12 %}{% endfor %}
|
|
||||||
|
|
||||||
* Do not put any spaces before and after the parenthesis used for filter and
|
|
||||||
function calls:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ foo|default('foo') }}
|
|
||||||
{{ range(1..10) }}
|
|
||||||
|
|
||||||
* Do not put any spaces before and after the opening and the closing of arrays
|
|
||||||
and hashes:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ [1, 2, 3] }}
|
|
||||||
{{ {'foo': 'bar'} }}
|
|
||||||
|
|
||||||
* Use lower cased and underscored variable names:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set foo = 'foo' %}
|
|
||||||
{% set foo_bar = 'foo' %}
|
|
||||||
|
|
||||||
* Indent your code inside tags (use the same indentation as the one used for
|
|
||||||
the main language of the file):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% block foo %}
|
|
||||||
{% if true %}
|
|
||||||
true
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -1,328 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
Creating an extension also makes for a better separation of code that is
|
|
||||||
executed at compilation time and code needed at runtime. As such, it makes
|
|
||||||
your code faster.
|
|
||||||
|
|
||||||
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 $environment The current Twig_Environment instance
|
|
||||||
*/
|
|
||||||
function initRuntime(Twig_Environment $environment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the token parser instances to add to the existing list.
|
|
||||||
*
|
|
||||||
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface 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 a list of functions to add to the existing list.
|
|
||||||
*
|
|
||||||
* @return array An array of functions
|
|
||||||
*/
|
|
||||||
function getFunctions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of operators to add to the existing list.
|
|
||||||
*
|
|
||||||
* @return array An array of operators
|
|
||||||
*/
|
|
||||||
function getOperators();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of global variables to add to the existing list.
|
|
||||||
*
|
|
||||||
* @return array An array of global variables
|
|
||||||
*/
|
|
||||||
function getGlobals();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the extension.
|
|
||||||
*
|
|
||||||
* @return string The extension name
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
-------
|
|
||||||
|
|
||||||
Global variables can be registered in an extension via the ``getGlobals()``
|
|
||||||
method::
|
|
||||||
|
|
||||||
class Project_Twig_Extension extends Twig_Extension
|
|
||||||
{
|
|
||||||
public function getGlobals()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'text' => new Text(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
Functions
|
|
||||||
---------
|
|
||||||
|
|
||||||
Functions can be registered in an extension via the ``getFunctions()``
|
|
||||||
method::
|
|
||||||
|
|
||||||
class Project_Twig_Extension extends Twig_Extension
|
|
||||||
{
|
|
||||||
public function getFunctions()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
'lipsum' => new Twig_Function_Function('generate_lipsum'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
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' => new 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
|
|
@ -1,11 +0,0 @@
|
|||||||
``capitalize``
|
|
||||||
==============
|
|
||||||
|
|
||||||
The ``capitalize`` filter capitalizes a value. The first character will be
|
|
||||||
uppercase, all others lowercase:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 'my first car'|capitalize }}
|
|
||||||
|
|
||||||
{# outputs 'My first car' #}
|
|
@ -1,22 +0,0 @@
|
|||||||
``convert_encoding``
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
|
||||||
The ``convert_encoding`` filter was added in Twig 1.4.
|
|
||||||
|
|
||||||
The ``convert_encoding`` filter converts a string from one encoding to
|
|
||||||
another. The first argument is the expected output charset and the second one
|
|
||||||
is the input charset:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This filter relies on the `iconv`_ or `mbstring`_ extension, so one of
|
|
||||||
them must be installed. In case both are installed, `iconv`_ is used
|
|
||||||
by default.
|
|
||||||
|
|
||||||
.. _`iconv`: http://php.net/iconv
|
|
||||||
.. _`mbstring`: http://php.net/mbstring
|
|
@ -1,65 +0,0 @@
|
|||||||
``date``
|
|
||||||
========
|
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
|
||||||
The timezone support has been added in Twig 1.1.
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The default date format support has been added in Twig 1.5.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6.1
|
|
||||||
The default timezone support has been added in Twig 1.6.1.
|
|
||||||
|
|
||||||
The ``date`` filter formats a date to a given format:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ post.published_at|date("m/d/Y") }}
|
|
||||||
|
|
||||||
The ``date`` filter accepts strings (it must be in a format supported by the
|
|
||||||
`date`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For
|
|
||||||
instance, to display the current date, filter the word "now":
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "now"|date("m/d/Y") }}
|
|
||||||
|
|
||||||
To escape words and characters in the date format use ``\\`` in front of each character:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ post.published_at|date("F jS \\a\\t g:ia") }}
|
|
||||||
|
|
||||||
You can also specify a timezone:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ post.published_at|date("m/d/Y", "Europe/Paris") }}
|
|
||||||
|
|
||||||
If no format is provided, Twig will use the default one: ``F j, Y H:i``. This
|
|
||||||
default can be easily changed by calling the ``setDateFormat()`` method on the
|
|
||||||
``core`` extension instance. The first argument is the default format for
|
|
||||||
dates and the second one is the default format for date intervals:
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->getExtension('core')->setDateFormat('d/m/Y', '%d days');
|
|
||||||
|
|
||||||
The default timezone can also be set globally by calling ``setTimezone()``:
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->getExtension('core')->setTimezone('Europe/Paris');
|
|
||||||
|
|
||||||
.. _`date`: http://www.php.net/date
|
|
||||||
.. _`DateTime`: http://www.php.net/DateTime
|
|
||||||
.. _`DateInterval`: http://www.php.net/DateInterval
|
|
||||||
|
|
||||||
If the value passed to the ``date`` filter is null, it will return the current date by default.
|
|
||||||
If an empty string is desired instead of the current date, use a ternary operator:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }}
|
|
@ -1,28 +0,0 @@
|
|||||||
``default``
|
|
||||||
===========
|
|
||||||
|
|
||||||
The ``default`` filter returns the passed default value if the value is
|
|
||||||
undefined or empty, otherwise the value of the variable:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var|default('var is not defined') }}
|
|
||||||
|
|
||||||
{{ var.foo|default('foo item on var is not defined') }}
|
|
||||||
|
|
||||||
{{ var['foo']|default('foo item on var is not defined') }}
|
|
||||||
|
|
||||||
{{ ''|default('passed var is empty') }}
|
|
||||||
|
|
||||||
When using the ``default`` filter on an expression that uses variables in some
|
|
||||||
method calls, be sure to use the ``default`` filter whenever a variable can be
|
|
||||||
undefined:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var.method(foo|default('foo'))|default('foo') }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Read the documentation for the :doc:`defined<../tests/defined>` and
|
|
||||||
:doc:`empty<../tests/empty>` tests to learn more about their semantics.
|
|
@ -1,30 +0,0 @@
|
|||||||
``escape``
|
|
||||||
==========
|
|
||||||
|
|
||||||
The ``escape`` filter converts the characters ``&``, ``<``, ``>``, ``'``, and
|
|
||||||
``"`` in strings to HTML-safe sequences. Use this if you need to display text
|
|
||||||
that might contain such characters in HTML:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ user.username|escape }}
|
|
||||||
|
|
||||||
For convenience, the ``e`` filter is defined as an alias:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ user.username|e }}
|
|
||||||
|
|
||||||
The ``escape`` filter can also be used in another context than HTML; for
|
|
||||||
instance, to escape variables included in a JavaScript:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ user.username|escape('js') }}
|
|
||||||
{{ user.username|e('js') }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function.
|
|
||||||
|
|
||||||
.. _`htmlspecialchars`: http://php.net/htmlspecialchars
|
|
@ -1,16 +0,0 @@
|
|||||||
``format``
|
|
||||||
==========
|
|
||||||
|
|
||||||
The ``format`` filter formats a given string by replacing the placeholders
|
|
||||||
(placeholders follows the `printf`_ notation):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "I like %s and %s."|format(foo, "bar") }}
|
|
||||||
|
|
||||||
{# returns I like foo and bar
|
|
||||||
if the foo parameter equals to the foo string. #}
|
|
||||||
|
|
||||||
.. _`printf`: http://www.php.net/printf
|
|
||||||
|
|
||||||
.. seealso:: :doc:`replace<replace>`
|
|
@ -1,29 +0,0 @@
|
|||||||
Filters
|
|
||||||
=======
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
date
|
|
||||||
format
|
|
||||||
replace
|
|
||||||
number_format
|
|
||||||
url_encode
|
|
||||||
json_encode
|
|
||||||
convert_encoding
|
|
||||||
title
|
|
||||||
capitalize
|
|
||||||
nl2br
|
|
||||||
upper
|
|
||||||
lower
|
|
||||||
striptags
|
|
||||||
join
|
|
||||||
reverse
|
|
||||||
length
|
|
||||||
sort
|
|
||||||
default
|
|
||||||
keys
|
|
||||||
escape
|
|
||||||
raw
|
|
||||||
merge
|
|
||||||
slice
|
|
@ -1,18 +0,0 @@
|
|||||||
``join``
|
|
||||||
========
|
|
||||||
|
|
||||||
The ``join`` filter returns a string which is the concatenation of the items
|
|
||||||
of a sequence:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ [1, 2, 3]|join }}
|
|
||||||
{# returns 123 #}
|
|
||||||
|
|
||||||
The separator between elements is an empty string per default, but you can
|
|
||||||
define it with the optional first parameter:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ [1, 2, 3]|join('|') }}
|
|
||||||
{# returns 1|2|3 #}
|
|
@ -1,14 +0,0 @@
|
|||||||
``json_encode``
|
|
||||||
===============
|
|
||||||
|
|
||||||
The ``json_encode`` filter returns the JSON representation of a string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ data|json_encode() }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `json_encode`_ function.
|
|
||||||
|
|
||||||
.. _`json_encode`: http://php.net/json_encode
|
|
@ -1,11 +0,0 @@
|
|||||||
``keys``
|
|
||||||
========
|
|
||||||
|
|
||||||
The ``keys`` filter returns the keys of an array. It is useful when you want to
|
|
||||||
iterate over the keys of an array:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for key in array|keys %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
@ -1,12 +0,0 @@
|
|||||||
``length``
|
|
||||||
==========
|
|
||||||
|
|
||||||
The ``length`` filters returns the number of items of a sequence or mapping, or
|
|
||||||
the length of a string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if users|length > 10 %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
|||||||
``lower``
|
|
||||||
=========
|
|
||||||
|
|
||||||
The ``lower`` filter converts a value to lowercase:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 'WELCOME'|lower }}
|
|
||||||
|
|
||||||
{# outputs 'welcome' #}
|
|
@ -1,41 +0,0 @@
|
|||||||
``merge``
|
|
||||||
=========
|
|
||||||
|
|
||||||
The ``merge`` filter merges an array with the another array:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set values = [1, 2] %}
|
|
||||||
|
|
||||||
{% set values = values|merge(['apple', 'orange']) %}
|
|
||||||
|
|
||||||
{# values now contains [1, 2, 'apple', 'orange'] #}
|
|
||||||
|
|
||||||
New values are added at the end of the existing ones.
|
|
||||||
|
|
||||||
The ``merge`` filter also works on hashes:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
|
|
||||||
|
|
||||||
{% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}
|
|
||||||
|
|
||||||
{# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #}
|
|
||||||
|
|
||||||
For hashes, the merging process occurs on the keys: if the key does not
|
|
||||||
already exist, it is added but if the key already exists, its value is
|
|
||||||
overridden.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
If you want to ensure that some values are defined in an array (by given
|
|
||||||
default values), reverse the two elements in the call:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
|
|
||||||
|
|
||||||
{% set items = { 'apple': 'unknown' }|merge(items) %}
|
|
||||||
|
|
||||||
{# items now contains { 'apple': 'fruit', 'orange': 'fruit' } #}
|
|
@ -1,22 +0,0 @@
|
|||||||
``nl2br``
|
|
||||||
=========
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The nl2br filter was added in Twig 1.5.
|
|
||||||
|
|
||||||
The ``nl2br`` filter inserts HTML line breaks before all newlines in a string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "I like Twig.\nYou will like it too."|nl2br }}
|
|
||||||
{# outputs
|
|
||||||
|
|
||||||
I like Twig.<br />
|
|
||||||
You will like it too.
|
|
||||||
|
|
||||||
#}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The ``nl2br`` filter pre-escapes the input before applying the
|
|
||||||
transformation.
|
|
@ -1,38 +0,0 @@
|
|||||||
``number_format``
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The number_format filter was added in Twig 1.5
|
|
||||||
|
|
||||||
The ``number_format`` filter formats numbers. It is a wrapper around PHP's
|
|
||||||
`number_format`_ function:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 200.35|number_format }}
|
|
||||||
|
|
||||||
You can control the number of decimal places, decimal point, and thousands
|
|
||||||
separator using the additional arguments:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 9800.333|number_format(2, ',', '.') }}
|
|
||||||
|
|
||||||
If no formatting options are provided then Twig will use the default formatting
|
|
||||||
options of:
|
|
||||||
|
|
||||||
- 0 decimal places.
|
|
||||||
- ``.`` as the decimal point.
|
|
||||||
- ``,`` as the thousands separator.
|
|
||||||
|
|
||||||
These defaults can be easily changed through the core extension:
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->getExtension('core')->setNumberFormat(3, ',', '.');
|
|
||||||
|
|
||||||
The defaults set for ``number_format`` can be over-ridden upon each call using the
|
|
||||||
additional parameters.
|
|
||||||
|
|
||||||
.. _`number_format`: http://php.net/number_format
|
|
@ -1,12 +0,0 @@
|
|||||||
``raw``
|
|
||||||
=======
|
|
||||||
|
|
||||||
The ``raw`` filter marks the value as being "safe", which means that in an
|
|
||||||
environment with automatic escaping enabled this variable will not be escaped
|
|
||||||
if ``raw`` is the last filter applied to it:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% autoescape true %}
|
|
||||||
{{ var|raw }} {# var won't be escaped #}
|
|
||||||
{% endautoescape %}
|
|
@ -1,14 +0,0 @@
|
|||||||
``replace``
|
|
||||||
===========
|
|
||||||
|
|
||||||
The ``replace`` filter formats a given string by replacing the placeholders
|
|
||||||
(placeholders are free-form):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
|
|
||||||
|
|
||||||
{# returns I like foo and bar
|
|
||||||
if the foo parameter equals to the foo string. #}
|
|
||||||
|
|
||||||
.. seealso:: :doc:`format<format>`
|
|
@ -1,23 +0,0 @@
|
|||||||
``reverse``
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
|
||||||
Support for strings has been added in Twig 1.6.
|
|
||||||
|
|
||||||
The ``reverse`` filter reverses a sequence, a mapping, or a string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for use in users|reverse %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{{ '1234'|reverse }}
|
|
||||||
|
|
||||||
{# outputs 4321 #}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It also works with objects implementing the `Traversable`_ interface.
|
|
||||||
|
|
||||||
.. _`Traversable`: http://php.net/Traversable
|
|
@ -1,57 +0,0 @@
|
|||||||
``slice``
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
|
||||||
The slice filter was added in Twig 1.6.
|
|
||||||
|
|
||||||
The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in [1, 2, 3, 4]|slice(1, 2) %}
|
|
||||||
{# will iterate over 2 and 3 #}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{{ '1234'|slice(1, 2) }}
|
|
||||||
|
|
||||||
{# outputs 23 #}
|
|
||||||
|
|
||||||
You can use any valid expression for both the start and the length:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in [1, 2, 3, 4]|slice(start, length) %}
|
|
||||||
{# ... #}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
As syntactic sugar, you can also use the ``[]`` notation:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in [1, 2, 3, 4][start:length] %}
|
|
||||||
{# ... #}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{{ '1234'[1:2] }}
|
|
||||||
|
|
||||||
The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
|
|
||||||
`substr`_ for strings.
|
|
||||||
|
|
||||||
If the start is non-negative, the sequence will start at that start in the
|
|
||||||
variable. If start is negative, the sequence will start that far from the end
|
|
||||||
of the variable.
|
|
||||||
|
|
||||||
If length is given and is positive, then the sequence will have up to that
|
|
||||||
many elements in it. If the variable is shorter than the length, then only the
|
|
||||||
available variable elements will be present. If length is given and is
|
|
||||||
negative then the sequence will stop that many elements from the end of the
|
|
||||||
variable. If it is omitted, then the sequence will have everything from offset
|
|
||||||
up until the end of the variable.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It also works with objects implementing the `Traversable`_ interface.
|
|
||||||
|
|
||||||
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
|
|
||||||
.. _`array_slice`: http://php.net/array_slice
|
|
||||||
.. _`substr`: http://php.net/substr
|
|
@ -1,17 +0,0 @@
|
|||||||
``sort``
|
|
||||||
========
|
|
||||||
|
|
||||||
The ``sort`` filter sorts an array:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for use in users|sort %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `asort`_ function to maintain index
|
|
||||||
association.
|
|
||||||
|
|
||||||
.. _`asort`: http://php.net/asort
|
|
@ -1,15 +0,0 @@
|
|||||||
``striptags``
|
|
||||||
=============
|
|
||||||
|
|
||||||
The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace
|
|
||||||
by one space:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% some_html|striptags %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `strip_tags`_ function.
|
|
||||||
|
|
||||||
.. _`strip_tags`: http://php.net/strip_tags
|
|
@ -1,11 +0,0 @@
|
|||||||
``title``
|
|
||||||
=========
|
|
||||||
|
|
||||||
The ``title`` filter returns a titlecased version of the value. Words will
|
|
||||||
start with uppercase letters, all remaining characters are lowercase:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 'my first car'|title }}
|
|
||||||
|
|
||||||
{# outputs 'My First Car' #}
|
|
@ -1,24 +0,0 @@
|
|||||||
``trim``
|
|
||||||
========
|
|
||||||
|
|
||||||
.. versionadded:: 1.6.2
|
|
||||||
The trim filter was added in Twig 1.6.2.
|
|
||||||
|
|
||||||
The ``trim`` filter strips whitespace (or other characters) from the beginning
|
|
||||||
and end of a string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ ' I like Twig. '|trim }}
|
|
||||||
|
|
||||||
{# outputs 'I like Twig.' #}
|
|
||||||
|
|
||||||
{{ ' I like Twig.'|trim('.') }}
|
|
||||||
|
|
||||||
{# outputs ' I like Twig' #}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `trim`_ function.
|
|
||||||
|
|
||||||
.. _`trim`: http://php.net/trim
|
|
@ -1,10 +0,0 @@
|
|||||||
``upper``
|
|
||||||
=========
|
|
||||||
|
|
||||||
The ``upper`` filter converts a value to uppercase:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ 'welcome'|upper }}
|
|
||||||
|
|
||||||
{# outputs 'WELCOME' #}
|
|
@ -1,14 +0,0 @@
|
|||||||
``url_encode``
|
|
||||||
==============
|
|
||||||
|
|
||||||
The ``url_encode`` filter URL encodes a given string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ data|url_encode() }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `urlencode`_ function.
|
|
||||||
|
|
||||||
.. _`urlencode`: http://php.net/urlencode
|
|
@ -1,18 +0,0 @@
|
|||||||
``attribute``
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
The ``attribute`` function was added in Twig 1.2.
|
|
||||||
|
|
||||||
``attribute`` can be used to access a "dynamic" attribute of a variable:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ attribute(object, method) }}
|
|
||||||
{{ attribute(object, method, arguments) }}
|
|
||||||
{{ attribute(array, item) }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The resolution algorithm is the same as the one used for the ``.``
|
|
||||||
notation, except that the item can be any valid expression.
|
|
@ -1,15 +0,0 @@
|
|||||||
``block``
|
|
||||||
=========
|
|
||||||
|
|
||||||
When a template uses inheritance and if you want to print a block multiple
|
|
||||||
times, use the ``block`` function:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<title>{% block title %}{% endblock %}</title>
|
|
||||||
|
|
||||||
<h1>{{ block('title') }}</h1>
|
|
||||||
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
|
|
||||||
.. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
|
|
@ -1,9 +0,0 @@
|
|||||||
``constant``
|
|
||||||
============
|
|
||||||
|
|
||||||
``constant`` returns the constant value for a given string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ some_date|date(constant('DATE_W3C')) }}
|
|
||||||
{{ constant('Namespace\\Classname::CONSTANT_NAME') }}
|
|
@ -1,20 +0,0 @@
|
|||||||
``cycle``
|
|
||||||
=========
|
|
||||||
|
|
||||||
The ``cycle`` function cycles on an array of values:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in 0..10 %}
|
|
||||||
{{ cycle(['odd', 'even'], i) }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
The array can contain any number of values:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set fruits = ['apple', 'orange', 'citrus'] %}
|
|
||||||
|
|
||||||
{% for i in 0..10 %}
|
|
||||||
{{ cycle(fruits, i) }}
|
|
||||||
{% endfor %}
|
|
@ -1,46 +0,0 @@
|
|||||||
``date``
|
|
||||||
========
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
|
||||||
The date function has been added in Twig 1.6.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6.1
|
|
||||||
The default timezone support has been added in Twig 1.6.1.
|
|
||||||
|
|
||||||
Converts an argument to a date to allow date comparison:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if date(user.created_at) < date('+2days') %}
|
|
||||||
{# do something #}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
The argument must be in a format supported by the `date`_ function.
|
|
||||||
|
|
||||||
You can pass a timezone as the second argument:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if date(user.created_at) < date('+2days', 'Europe/Paris') %}
|
|
||||||
{# do something #}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
If no argument is passed, the function returns the current date:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if date(user.created_at) < date() %}
|
|
||||||
{# always! #}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
You can set the default timezone globally by calling ``setTimezone()`` on
|
|
||||||
the ``core`` extension instance:
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
$twig->getExtension('core')->setTimezone('Europe/Paris');
|
|
||||||
|
|
||||||
.. _`date`: http://www.php.net/date
|
|
@ -1,58 +0,0 @@
|
|||||||
``dump``
|
|
||||||
========
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The dump function was added in Twig 1.5.
|
|
||||||
|
|
||||||
The ``dump`` function dumps information about a template variable. This is
|
|
||||||
mostly useful to debug a template that does not behave as expected by
|
|
||||||
introspecting its variables:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ dump(user) }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The ``debug`` function is not available by default. You must load it explicitly::
|
|
||||||
|
|
||||||
$twig = new Twig_Environment($loader, $config);
|
|
||||||
$twig->addExtension(new Twig_Extension_Debug());
|
|
||||||
|
|
||||||
Even when loaded explicitly, it won't do anything if the ``debug`` option
|
|
||||||
is not enabled.
|
|
||||||
|
|
||||||
In an HTML context, wrap the output with a ``pre`` tag to make it easier to
|
|
||||||
read:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
{{ dump(user) }}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
Using a ``pre`` tag is not needed when `XDebug`_ is enabled and
|
|
||||||
``html_errors`` is ``on``; as a bonus, the output is also nicer with
|
|
||||||
XDebug enabled.
|
|
||||||
|
|
||||||
You can debug several variables by passing them as additional arguments:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ dump(user, categories) }}
|
|
||||||
|
|
||||||
If you don't pass any value, all variables from the current context are
|
|
||||||
dumped:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ dump() }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `var_dump`_ function.
|
|
||||||
|
|
||||||
.. _`XDebug`: http://xdebug.org/docs/display
|
|
||||||
.. _`var_dump`: http://php.net/var_dump
|
|
@ -1,15 +0,0 @@
|
|||||||
Functions
|
|
||||||
=========
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
range
|
|
||||||
cycle
|
|
||||||
constant
|
|
||||||
random
|
|
||||||
attribute
|
|
||||||
block
|
|
||||||
parent
|
|
||||||
dump
|
|
||||||
date
|
|
@ -1,20 +0,0 @@
|
|||||||
``parent``
|
|
||||||
==========
|
|
||||||
|
|
||||||
When a template uses inheritance, it's possible to render the contents of the
|
|
||||||
parent block when overriding a block by using the ``parent`` function:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
<h3>Table Of Contents</h3>
|
|
||||||
...
|
|
||||||
{{ parent() }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
The ``parent()`` call will return the content of the ``sidebar`` block as
|
|
||||||
defined in the ``base.html`` template.
|
|
||||||
|
|
||||||
.. seealso:: :doc:`extends<../tags/extends>`, :doc:`block<../functions/block>`, :doc:`block<../tags/block>`
|
|
@ -1,24 +0,0 @@
|
|||||||
``random``
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The random function was added in Twig 1.5.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
|
||||||
String and integer handling was added in Twig 1.6.
|
|
||||||
|
|
||||||
The ``random`` function returns a random value depending on the supplied
|
|
||||||
parameter type:
|
|
||||||
|
|
||||||
* a random item from a sequence;
|
|
||||||
* a random character from a string;
|
|
||||||
* a random integer between 0 and the integer parameter (inclusive).
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
|
|
||||||
{{ random('ABC') }} {# example output: C #}
|
|
||||||
{{ random() }} {# example output: 15386094 (works as native PHP `mt_rand`_ function) #}
|
|
||||||
{{ random(5) }} {# example output: 3 #}
|
|
||||||
|
|
||||||
.. _`mt_rand`: http://php.net/mt_rand
|
|
@ -1,38 +0,0 @@
|
|||||||
``range``
|
|
||||||
=========
|
|
||||||
|
|
||||||
Returns a list containing an arithmetic progression of integers:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in range(0, 3) %}
|
|
||||||
{{ i }},
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{# returns 0, 1, 2, 3 #}
|
|
||||||
|
|
||||||
When step is given (as the third parameter), it specifies the increment (or
|
|
||||||
decrement):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in range(0, 6, 2) %}
|
|
||||||
{{ i }},
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{# returns 0, 2, 4, 6 #}
|
|
||||||
|
|
||||||
The Twig built-in ``..`` operator is just syntactic sugar for the ``range``
|
|
||||||
function (with a step of 1):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in 0..3 %}
|
|
||||||
{{ i }},
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
The ``range`` function works as the native PHP `range`_ function.
|
|
||||||
|
|
||||||
.. _`range`: http://php.net/range
|
|
@ -1,184 +0,0 @@
|
|||||||
Hacking Twig
|
|
||||||
============
|
|
||||||
|
|
||||||
Twig is very extensible and you can easily hack it. Keep in mind that you
|
|
||||||
should probably try to create an extension before hacking the core, as most
|
|
||||||
features and enhancements can be done with extensions. This chapter is also
|
|
||||||
useful for people who want to understand how Twig works under the hood.
|
|
||||||
|
|
||||||
How Twig works?
|
|
||||||
---------------
|
|
||||||
|
|
||||||
The rendering of a Twig template can be summarized into four key steps:
|
|
||||||
|
|
||||||
* **Load** the template: If the template is already compiled, load it and go
|
|
||||||
to the *evaluation* step, otherwise:
|
|
||||||
|
|
||||||
* First, the **lexer** tokenizes the template source code into small pieces
|
|
||||||
for easier processing;
|
|
||||||
* Then, the **parser** converts the token stream into a meaningful tree
|
|
||||||
of nodes (the Abstract Syntax Tree);
|
|
||||||
* Eventually, the *compiler* transforms the AST into PHP code;
|
|
||||||
|
|
||||||
* **Evaluate** the template: It basically means calling the ``display()``
|
|
||||||
method of the compiled template and passing it the context.
|
|
||||||
|
|
||||||
The Lexer
|
|
||||||
---------
|
|
||||||
|
|
||||||
The Twig lexer goal is to tokenize a source code into a token stream (each
|
|
||||||
token is of class ``Token``, and the stream is an instance of
|
|
||||||
``Twig_TokenStream``). The default lexer recognizes nine different token types:
|
|
||||||
|
|
||||||
* ``Twig_Token::TEXT_TYPE``
|
|
||||||
* ``Twig_Token::BLOCK_START_TYPE``
|
|
||||||
* ``Twig_Token::VAR_START_TYPE``
|
|
||||||
* ``Twig_Token::BLOCK_END_TYPE``
|
|
||||||
* ``Twig_Token::VAR_END_TYPE``
|
|
||||||
* ``Twig_Token::NAME_TYPE``
|
|
||||||
* ``Twig_Token::NUMBER_TYPE``
|
|
||||||
* ``Twig_Token::STRING_TYPE``
|
|
||||||
* ``Twig_Token::OPERATOR_TYPE``
|
|
||||||
* ``Twig_Token::EOF_TYPE``
|
|
||||||
|
|
||||||
You can manually convert a source code into a token stream by calling the
|
|
||||||
``tokenize()`` of an environment::
|
|
||||||
|
|
||||||
$stream = $twig->tokenize($source, $identifier);
|
|
||||||
|
|
||||||
As the stream has a ``__toString()`` method, you can have a textual
|
|
||||||
representation of it by echoing the object::
|
|
||||||
|
|
||||||
echo $stream."\n";
|
|
||||||
|
|
||||||
Here is the output for the ``Hello {{ name }}`` template:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
TEXT_TYPE(Hello )
|
|
||||||
VAR_START_TYPE()
|
|
||||||
NAME_TYPE(name)
|
|
||||||
VAR_END_TYPE()
|
|
||||||
EOF_TYPE()
|
|
||||||
|
|
||||||
You can change the default lexer use by Twig (``Twig_Lexer``) by calling the
|
|
||||||
``setLexer()`` method::
|
|
||||||
|
|
||||||
$twig->setLexer($lexer);
|
|
||||||
|
|
||||||
Lexer classes must implement the ``Twig_LexerInterface``::
|
|
||||||
|
|
||||||
interface Twig_LexerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Tokenizes a source code.
|
|
||||||
*
|
|
||||||
* @param string $code The source code
|
|
||||||
* @param string $filename A unique identifier for the source code
|
|
||||||
*
|
|
||||||
* @return Twig_TokenStream A token stream instance
|
|
||||||
*/
|
|
||||||
function tokenize($code, $filename = 'n/a');
|
|
||||||
}
|
|
||||||
|
|
||||||
The Parser
|
|
||||||
----------
|
|
||||||
|
|
||||||
The parser converts the token stream into an AST (Abstract Syntax Tree), or a
|
|
||||||
node tree (of class ``Twig_Node_Module``). The core extension defines the
|
|
||||||
basic nodes like: ``for``, ``if``, ... and the expression nodes.
|
|
||||||
|
|
||||||
You can manually convert a token stream into a node tree by calling the
|
|
||||||
``parse()`` method of an environment::
|
|
||||||
|
|
||||||
$nodes = $twig->parse($stream);
|
|
||||||
|
|
||||||
Echoing the node object gives you a nice representation of the tree::
|
|
||||||
|
|
||||||
echo $nodes."\n";
|
|
||||||
|
|
||||||
Here is the output for the ``Hello {{ name }}`` template:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
Twig_Node_Module(
|
|
||||||
Twig_Node_Text(Hello )
|
|
||||||
Twig_Node_Print(
|
|
||||||
Twig_Node_Expression_Name(name)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
The default parser (``Twig_TokenParser``) can be also changed by calling the
|
|
||||||
``setParser()`` method::
|
|
||||||
|
|
||||||
$twig->setParser($parser);
|
|
||||||
|
|
||||||
All Twig parsers must implement the ``Twig_ParserInterface``::
|
|
||||||
|
|
||||||
interface Twig_ParserInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Converts a token stream to a node tree.
|
|
||||||
*
|
|
||||||
* @param Twig_TokenStream $stream A token stream instance
|
|
||||||
*
|
|
||||||
* @return Twig_Node_Module A node tree
|
|
||||||
*/
|
|
||||||
function parser(Twig_TokenStream $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
The Compiler
|
|
||||||
------------
|
|
||||||
|
|
||||||
The last step is done by the compiler. It takes a node tree as an input and
|
|
||||||
generates PHP code usable for runtime execution of the templates. The default
|
|
||||||
compiler generates PHP classes to ease the implementation of the template
|
|
||||||
inheritance feature.
|
|
||||||
|
|
||||||
You can call the compiler by hand with the ``compile()`` method of an
|
|
||||||
environment::
|
|
||||||
|
|
||||||
$php = $twig->compile($nodes);
|
|
||||||
|
|
||||||
The ``compile()`` method returns the PHP source code representing the node.
|
|
||||||
|
|
||||||
The generated template for a ``Hello {{ name }}`` template reads as follows::
|
|
||||||
|
|
||||||
/* Hello {{ name }} */
|
|
||||||
class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template
|
|
||||||
{
|
|
||||||
public function display($context)
|
|
||||||
{
|
|
||||||
$this->env->initRuntime();
|
|
||||||
|
|
||||||
// line 1
|
|
||||||
echo "Hello ";
|
|
||||||
echo (isset($context['name']) ? $context['name'] : null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
As for the lexer and the parser, the default compiler (``Twig_Compiler``) can
|
|
||||||
be changed by calling the ``setCompiler()`` method::
|
|
||||||
|
|
||||||
$twig->setCompiler($compiler);
|
|
||||||
|
|
||||||
All Twig compilers must implement the ``Twig_CompilerInterface``::
|
|
||||||
|
|
||||||
interface Twig_CompilerInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Compiles a node.
|
|
||||||
*
|
|
||||||
* @param Twig_Node $node The node to compile
|
|
||||||
*
|
|
||||||
* @return Twig_Compiler The current compiler instance
|
|
||||||
*/
|
|
||||||
function compile(Twig_Node $node);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current PHP code after compilation.
|
|
||||||
*
|
|
||||||
* @return string The PHP code
|
|
||||||
*/
|
|
||||||
function getSource();
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
Twig
|
|
||||||
====
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
intro
|
|
||||||
templates
|
|
||||||
api
|
|
||||||
advanced
|
|
||||||
extensions
|
|
||||||
hacking
|
|
||||||
recipes
|
|
||||||
coding_standards
|
|
||||||
tags/index
|
|
||||||
filters/index
|
|
||||||
functions/index
|
|
||||||
tests/index
|
|
@ -1,153 +0,0 @@
|
|||||||
Introduction
|
|
||||||
============
|
|
||||||
|
|
||||||
This is the documentation for Twig, the flexible, fast, and secure template
|
|
||||||
engine for PHP.
|
|
||||||
|
|
||||||
If you have any exposure to other text-based template languages, such as
|
|
||||||
Smarty, Django, or Jinja, you should feel right at home with Twig. It's both
|
|
||||||
designer and developer friendly by sticking to PHP's principles and adding
|
|
||||||
functionality useful for templating environments.
|
|
||||||
|
|
||||||
The key-features are...
|
|
||||||
|
|
||||||
* *Fast*: Twig compiles templates down to plain optimized PHP code. The
|
|
||||||
overhead compared to regular PHP code was reduced to the very minimum.
|
|
||||||
|
|
||||||
* *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This
|
|
||||||
allows Twig to be used as a template language for applications where users
|
|
||||||
may modify the template design.
|
|
||||||
|
|
||||||
* *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
|
|
||||||
developer to define its own custom tags and filters, and create its own DSL.
|
|
||||||
|
|
||||||
Prerequisites
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Twig needs at least **PHP 5.2.4** to run.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
You have multiple ways to install Twig. If you are unsure what to do, go with
|
|
||||||
the tarball.
|
|
||||||
|
|
||||||
Installing from the tarball release
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
1. Download the most recent tarball from the `download page`_
|
|
||||||
2. Unpack the tarball
|
|
||||||
3. Move the files somewhere in your project
|
|
||||||
|
|
||||||
Installing the development version
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
1. Install Subversion or Git
|
|
||||||
2. For Git: ``git clone git://github.com/fabpot/Twig.git``
|
|
||||||
3. For Subversion: ``svn co http://svn.twig-project.org/trunk/ twig``
|
|
||||||
|
|
||||||
Installing the PEAR package
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
1. Install PEAR
|
|
||||||
2. ``pear channel-discover pear.twig-project.org``
|
|
||||||
3. ``pear install twig/Twig`` (or ``pear install twig/Twig-beta``)
|
|
||||||
|
|
||||||
Installing via Composer
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
1. Install composer in your project:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
curl -s http://getcomposer.org/installer | php``
|
|
||||||
|
|
||||||
2. Create a ``composer.json`` file in your project root:
|
|
||||||
|
|
||||||
.. code-block:: javascript
|
|
||||||
|
|
||||||
{
|
|
||||||
"require": {
|
|
||||||
"twig/twig": "1.6.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
3. Install via composer
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
php composer.phar install
|
|
||||||
|
|
||||||
Installing the C extension
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
|
||||||
The C extension was added in Twig 1.4.
|
|
||||||
|
|
||||||
Twig comes with a C extension that enhances the performance of the Twig
|
|
||||||
runtime engine. You can install it like any other PHP extension:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ cd ext/twig
|
|
||||||
$ phpize
|
|
||||||
$ ./configure
|
|
||||||
$ make
|
|
||||||
$ make install
|
|
||||||
|
|
||||||
Finally, enable the extension in your ``php.ini`` configuration file:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
extension=twig.so
|
|
||||||
|
|
||||||
And from now on, Twig will automatically compile your templates to take
|
|
||||||
advantage of the C extension.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
On Windows, you can also simply download and install a `pre-build DLL`_.
|
|
||||||
|
|
||||||
Basic API Usage
|
|
||||||
---------------
|
|
||||||
|
|
||||||
This section gives you a brief introduction to the PHP API for Twig.
|
|
||||||
|
|
||||||
The first step to use Twig is to register its autoloader::
|
|
||||||
|
|
||||||
require_once '/path/to/lib/Twig/Autoloader.php';
|
|
||||||
Twig_Autoloader::register();
|
|
||||||
|
|
||||||
Replace the ``/path/to/lib/`` path with the path you used for Twig
|
|
||||||
installation.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Twig follows the PEAR convention names for its classes, which means you
|
|
||||||
can easily integrate Twig classes loading in your own autoloader.
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_String();
|
|
||||||
$twig = new Twig_Environment($loader);
|
|
||||||
|
|
||||||
echo $twig->render('Hello {{ name }}!', array('name' => 'Fabien'));
|
|
||||||
|
|
||||||
Twig uses a loader (``Twig_Loader_String``) to locate templates, and an
|
|
||||||
environment (``Twig_Environment``) to store the configuration.
|
|
||||||
|
|
||||||
The ``render()`` method loads the template passed as a first argument and
|
|
||||||
renders it with the variables passed as a second argument.
|
|
||||||
|
|
||||||
As templates are generally stored on the filesystem, Twig also comes with a
|
|
||||||
filesystem loader::
|
|
||||||
|
|
||||||
$loader = new Twig_Loader_Filesystem('/path/to/templates');
|
|
||||||
$twig = new Twig_Environment($loader, array(
|
|
||||||
'cache' => '/path/to/compilation_cache',
|
|
||||||
));
|
|
||||||
|
|
||||||
echo $twig->render('index.html', array('name' => 'Fabien'));
|
|
||||||
|
|
||||||
.. _`download page`: https://github.com/fabpot/Twig/tags
|
|
||||||
.. _`pre-build DLL`: https://github.com/stealth35/stealth35.github.com/downloads
|
|
@ -1,301 +0,0 @@
|
|||||||
Recipes
|
|
||||||
=======
|
|
||||||
|
|
||||||
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('{{', '}}'),
|
|
||||||
));
|
|
||||||
$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 providing 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
|
|
||||||
}
|
|
||||||
|
|
||||||
Refreshing modified Templates when APC is enabled and apc.stat = 0
|
|
||||||
------------------------------------------------------------------
|
|
||||||
|
|
||||||
When using APC with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing
|
|
||||||
the template cache won't update the APC cache. To get around this, one can
|
|
||||||
extend ``Twig_Environment`` and force the update of the APC cache when Twig
|
|
||||||
rewrites the cache::
|
|
||||||
|
|
||||||
class Twig_Environment_APC extends Twig_Environment
|
|
||||||
{
|
|
||||||
protected function writeCacheFile($file, $content)
|
|
||||||
{
|
|
||||||
parent::writeCacheFile($file, $content);
|
|
||||||
|
|
||||||
// Compile cached file into bytecode cache
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.. _callback: http://www.php.net/manual/en/function.is-callable.php
|
|
@ -1,43 +0,0 @@
|
|||||||
``autoescape``
|
|
||||||
==============
|
|
||||||
|
|
||||||
Whether automatic escaping is enabled or not, you can mark a section of a
|
|
||||||
template to be escaped or not by using the ``autoescape`` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% autoescape true %}
|
|
||||||
Everything will be automatically escaped in this block
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
{% autoescape false %}
|
|
||||||
Everything will be outputed as is in this block
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
{% autoescape true js %}
|
|
||||||
Everything will be automatically escaped in this block
|
|
||||||
using the js escaping strategy
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
When automatic escaping is enabled everything is escaped by default except for
|
|
||||||
values explicitly marked as safe. Those can be marked in the template by using
|
|
||||||
the :doc:`raw<../filters/raw>` filter:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% autoescape true %}
|
|
||||||
{{ safe_value|raw }}
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
Functions returning template data (like :doc:`macros<macro>` and
|
|
||||||
:doc:`parent<../functions/parent>`) always return safe markup.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Twig is smart enough to not escape an already escaped value by the
|
|
||||||
:doc:`escape<../filters/escape>` filter.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The chapter :doc:`Twig for Developers<../api>` gives more information
|
|
||||||
about when and how automatic escaping is applied.
|
|
@ -1,11 +0,0 @@
|
|||||||
``block``
|
|
||||||
=========
|
|
||||||
|
|
||||||
Blocks are used for inheritance and act as placeholders and replacements at
|
|
||||||
the same time. They are documented in detail in the documentation for the
|
|
||||||
:doc:`extends<../tags/extends>` tag.
|
|
||||||
|
|
||||||
Block names should consist of alphanumeric characters, and underscores. Dashes
|
|
||||||
are not permitted.
|
|
||||||
|
|
||||||
.. seealso:: :doc:`block<../functions/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`, :doc:`extends<../tags/extends>`
|
|
@ -1,12 +0,0 @@
|
|||||||
``do``
|
|
||||||
======
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The do tag was added in Twig 1.5.
|
|
||||||
|
|
||||||
The ``do`` tag works exactly like the regular variable expression (``{{ ...
|
|
||||||
}}``) just that it doesn't print anything:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% do 1 + 2 %}
|
|
@ -1,187 +0,0 @@
|
|||||||
``extends``
|
|
||||||
===========
|
|
||||||
|
|
||||||
The ``extends`` tag can be used to extend a template from another one.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Like PHP, Twig does not support multiple inheritance. So you can only have
|
|
||||||
one extends tag called per rendering. However, Twig supports horizontal
|
|
||||||
:doc:`reuse<use>`.
|
|
||||||
|
|
||||||
Let's define a base template, ``base.html``, which defines a simple HTML
|
|
||||||
skeleton document:
|
|
||||||
|
|
||||||
.. code-block:: html+jinja
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% block head %}
|
|
||||||
<link rel="stylesheet" href="style.css" />
|
|
||||||
<title>{% block title %}{% endblock %} - My Webpage</title>
|
|
||||||
{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content">{% block content %}{% endblock %}</div>
|
|
||||||
<div id="footer">
|
|
||||||
{% block footer %}
|
|
||||||
© Copyright 2011 by <a href="http://domain.invalid/">you</a>.
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
In this example, the :doc:`block<block>` tags define four blocks that child
|
|
||||||
templates can fill in. All the ``block`` tag does is to tell the template
|
|
||||||
engine that a child template may override those portions of the template.
|
|
||||||
|
|
||||||
Child Template
|
|
||||||
--------------
|
|
||||||
|
|
||||||
A child template might look like this:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Index{% endblock %}
|
|
||||||
{% block head %}
|
|
||||||
{{ parent() }}
|
|
||||||
<style type="text/css">
|
|
||||||
.important { color: #336699; }
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<h1>Index</h1>
|
|
||||||
<p class="important">
|
|
||||||
Welcome on my awesome homepage.
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
The ``extends`` tag is the key here. It tells the template engine that this
|
|
||||||
template "extends" another template. When the template system evaluates this
|
|
||||||
template, first it locates the parent. The extends tag should be the first tag
|
|
||||||
in the template.
|
|
||||||
|
|
||||||
Note that since the child template doesn't define the ``footer`` block, the
|
|
||||||
value from the parent template is used instead.
|
|
||||||
|
|
||||||
You can't define multiple ``block`` tags with the same name in the same
|
|
||||||
template. This limitation exists because a block tag works in "both"
|
|
||||||
directions. That is, a block tag doesn't just provide a hole to fill - it also
|
|
||||||
defines the content that fills the hole in the *parent*. If there were two
|
|
||||||
similarly-named ``block`` tags in a template, that template's parent wouldn't
|
|
||||||
know which one of the blocks' content to use.
|
|
||||||
|
|
||||||
If you want to print a block multiple times you can however use the
|
|
||||||
``block`` function:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<title>{% block title %}{% endblock %}</title>
|
|
||||||
<h1>{{ block('title') }}</h1>
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
|
|
||||||
Parent Blocks
|
|
||||||
-------------
|
|
||||||
|
|
||||||
It's possible to render the contents of the parent block by using the
|
|
||||||
:doc:`parent<../functions/parent>` function. This gives back the results of
|
|
||||||
the parent block:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
<h3>Table Of Contents</h3>
|
|
||||||
...
|
|
||||||
{{ parent() }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
Named Block End-Tags
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Twig allows you to put the name of the block after the end tag for better
|
|
||||||
readability:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{% block inner_sidebar %}
|
|
||||||
...
|
|
||||||
{% endblock inner_sidebar %}
|
|
||||||
{% endblock sidebar %}
|
|
||||||
|
|
||||||
Of course, the name after the ``endblock`` word must match the block name.
|
|
||||||
|
|
||||||
Block Nesting and Scope
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Blocks can be nested for more complex layouts. Per default, blocks have access
|
|
||||||
to variables from outer scopes:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for item in seq %}
|
|
||||||
<li>{% block loop_item %}{{ item }}{% endblock %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
Block Shortcuts
|
|
||||||
---------------
|
|
||||||
|
|
||||||
For blocks with few content, it's possible to use a shortcut syntax. The
|
|
||||||
following constructs do the same:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{{ page_title|title }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% block title page_title|title %}
|
|
||||||
|
|
||||||
Dynamic Inheritance
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Twig supports dynamic inheritance by using a variable as the base template:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends some_var %}
|
|
||||||
|
|
||||||
If the variable evaluates to a ``Twig_Template`` object, Twig will use it as
|
|
||||||
the parent template::
|
|
||||||
|
|
||||||
// {% extends layout %}
|
|
||||||
|
|
||||||
$layout = $twig->loadTemplate('some_layout_template.twig');
|
|
||||||
|
|
||||||
$twig->display('template.twig', array('layout' => $layout));
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
The possibility to pass an array of templates has been added in Twig 1.2.
|
|
||||||
|
|
||||||
You can also provide a list of templates that are checked for existence. The
|
|
||||||
first template that exists will be used as a parent:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends ['layout.html', 'base_layout.html'] %}
|
|
||||||
|
|
||||||
Conditional Inheritance
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
As the template name for the parent can be any valid Twig expression, it's
|
|
||||||
possible to make the inheritance mechanism conditional:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends standalone ? "minimum.html" : "base.html" %}
|
|
||||||
|
|
||||||
In this example, the template will extend the "minimum.html" layout template
|
|
||||||
if the ``standalone`` variable evaluates to ``true``, and "base.html"
|
|
||||||
otherwise.
|
|
||||||
|
|
||||||
.. seealso:: :doc:`block<../functions/block>`, :doc:`block<../tags/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`
|
|
@ -1,21 +0,0 @@
|
|||||||
``filter``
|
|
||||||
==========
|
|
||||||
|
|
||||||
Filter sections allow you to apply regular Twig filters on a block of template
|
|
||||||
data. Just wrap the code in the special ``filter`` section:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% filter upper %}
|
|
||||||
This text becomes uppercase
|
|
||||||
{% endfilter %}
|
|
||||||
|
|
||||||
You can also chain filters:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% filter lower|escape %}
|
|
||||||
<strong>SOME TEXT</strong>
|
|
||||||
{% endfilter %}
|
|
||||||
|
|
||||||
{# outputs "<strong>some text</strong>" #}
|
|
@ -1,17 +0,0 @@
|
|||||||
``flush``
|
|
||||||
=========
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
The flush tag was added in Twig 1.5.
|
|
||||||
|
|
||||||
The ``flush`` tag tells Twig to flush the output buffer:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% flush %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Internally, Twig uses the PHP `flush`_ function.
|
|
||||||
|
|
||||||
.. _`flush`: http://php.net/flush
|
|
@ -1,149 +0,0 @@
|
|||||||
``for``
|
|
||||||
=======
|
|
||||||
|
|
||||||
Loop over each item in a sequence. For example, to display a list of users
|
|
||||||
provided in a variable called ``users``:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<h1>Members</h1>
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li>{{ user.username|e }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
A sequence can be either an array or an object implementing the
|
|
||||||
``Traversable`` interface.
|
|
||||||
|
|
||||||
If you do need to iterate over a sequence of numbers, you can use the ``..``
|
|
||||||
operator:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in 0..10 %}
|
|
||||||
* {{ i }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
The above snippet of code would print all numbers from 0 to 10.
|
|
||||||
|
|
||||||
It can be also useful with letters:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for letter in 'a'..'z' %}
|
|
||||||
* {{ letter }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
The ``..`` operator can take any expression at both sides:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for letter in 'a'|upper..'z'|upper %}
|
|
||||||
* {{ letter }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
.. tip:
|
|
||||||
|
|
||||||
If you need a step different from 1, you can use the ``range`` function
|
|
||||||
instead.
|
|
||||||
|
|
||||||
The `loop` variable
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Inside of a ``for`` loop block you can access some special variables:
|
|
||||||
|
|
||||||
===================== =============================================================
|
|
||||||
Variable Description
|
|
||||||
===================== =============================================================
|
|
||||||
``loop.index`` The current iteration of the loop. (1 indexed)
|
|
||||||
``loop.index0`` The current iteration of the loop. (0 indexed)
|
|
||||||
``loop.revindex`` The number of iterations from the end of the loop (1 indexed)
|
|
||||||
``loop.revindex0`` The number of iterations from the end of the loop (0 indexed)
|
|
||||||
``loop.first`` True if first iteration
|
|
||||||
``loop.last`` True if last iteration
|
|
||||||
``loop.length`` The number of items in the sequence
|
|
||||||
``loop.parent`` The parent context
|
|
||||||
===================== =============================================================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and
|
|
||||||
``loop.last`` variables are only available for PHP arrays, or objects that
|
|
||||||
implement the ``Countable`` interface.
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
The ``if`` modifier support has been added in Twig 1.2.
|
|
||||||
|
|
||||||
Adding a condition
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Unlike in PHP, it's not possible to ``break`` or ``continue`` in a loop. You
|
|
||||||
can however filter the sequence during iteration which allows you to skip
|
|
||||||
items. The following example skips all the users which are not active:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for user in users if user.active %}
|
|
||||||
<li>{{ user.username|e }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
The advantage is that the special loop variable will count correctly thus not
|
|
||||||
counting the users not iterated over.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Using the ``loop`` variable within the condition is not recommended as it
|
|
||||||
will probably not be doing what you expect it to. For instance, adding a
|
|
||||||
condition like ``loop.index > 4`` won't work as the index is only
|
|
||||||
incremented when the condition is true (so the condition will never
|
|
||||||
match).
|
|
||||||
|
|
||||||
The `else` Clause
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
If no iteration took place because the sequence was empty, you can render a
|
|
||||||
replacement block by using ``else``:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li>{{ user.username|e }}</li>
|
|
||||||
{% else %}
|
|
||||||
<li><em>no user found</em></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Iterating over Keys
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
By default, a loop iterates over the values of the sequence. You can iterate
|
|
||||||
on keys by using the ``keys`` filter:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<h1>Members</h1>
|
|
||||||
<ul>
|
|
||||||
{% for key in users|keys %}
|
|
||||||
<li>{{ key }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Iterating over Keys and Values
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
You can also access both keys and values:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<h1>Members</h1>
|
|
||||||
<ul>
|
|
||||||
{% for key, user in users %}
|
|
||||||
<li>{{ key }}: {{ user.username|e }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
@ -1,8 +0,0 @@
|
|||||||
``from``
|
|
||||||
========
|
|
||||||
|
|
||||||
The ``from`` tags import :doc:`macro<../tags/macro>` names into the current
|
|
||||||
namespace. The tag is documented in detail in the documentation for the
|
|
||||||
:doc:`import<../tags/import>` tag.
|
|
||||||
|
|
||||||
.. seealso:: :doc:`macro<../tags/macro>`, :doc:`import<../tags/import>`
|
|
@ -1,43 +0,0 @@
|
|||||||
``if``
|
|
||||||
======
|
|
||||||
|
|
||||||
The ``if`` statement in Twig is comparable with the if statements of PHP.
|
|
||||||
|
|
||||||
In the simplest form you can use it to test if an expression evaluates to
|
|
||||||
``true``:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if online == false %}
|
|
||||||
<p>Our website is in maintenance mode. Please, come back later.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
You can also test if an array is not empty:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if users %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li>{{ user.username|e }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you want to test if the variable is defined, use ``if users is
|
|
||||||
defined`` instead.
|
|
||||||
|
|
||||||
For multiple branches ``elseif`` and ``else`` can be used like in PHP. You can use
|
|
||||||
more complex ``expressions`` there too:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if kenny.sick %}
|
|
||||||
Kenny is sick.
|
|
||||||
{% elseif kenny.dead %}
|
|
||||||
You killed Kenny! You bastard!!!
|
|
||||||
{% else %}
|
|
||||||
Kenny looks okay --- so far
|
|
||||||
{% endif %}
|
|
@ -1,79 +0,0 @@
|
|||||||
``import``
|
|
||||||
==========
|
|
||||||
|
|
||||||
Twig supports putting often used code into :doc:`macros<../tags/macro>`. These
|
|
||||||
macros can go into different templates and get imported from there.
|
|
||||||
|
|
||||||
There are two ways to import templates. You can import the complete template
|
|
||||||
into a variable or request specific macros from it.
|
|
||||||
|
|
||||||
Imagine we have a helper module that renders forms (called ``forms.html``):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% macro input(name, value, type, size) %}
|
|
||||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro textarea(name, value, rows) %}
|
|
||||||
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
The easiest and most flexible is importing the whole module into a variable.
|
|
||||||
That way you can access the attributes:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% import 'forms.html' as forms %}
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>Username</dt>
|
|
||||||
<dd>{{ forms.input('username') }}</dd>
|
|
||||||
<dt>Password</dt>
|
|
||||||
<dd>{{ forms.input('password', null, 'password') }}</dd>
|
|
||||||
</dl>
|
|
||||||
<p>{{ forms.textarea('comment') }}</p>
|
|
||||||
|
|
||||||
Alternatively you can import names from the template into the current
|
|
||||||
namespace:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% from 'forms.html' import input as input_field, textarea %}
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>Username</dt>
|
|
||||||
<dd>{{ input_field('username') }}</dd>
|
|
||||||
<dt>Password</dt>
|
|
||||||
<dd>{{ input_field('password', '', 'password') }}</dd>
|
|
||||||
</dl>
|
|
||||||
<p>{{ textarea('comment') }}</p>
|
|
||||||
|
|
||||||
Importing is not needed if the macros and the template are defined in the same
|
|
||||||
file; use the special ``_self`` variable instead:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# index.html template #}
|
|
||||||
|
|
||||||
{% macro textarea(name, value, rows) %}
|
|
||||||
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
<p>{{ _self.textarea('comment') }}</p>
|
|
||||||
|
|
||||||
But you can still create an alias by importing from the ``_self`` variable:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# index.html template #}
|
|
||||||
|
|
||||||
{% macro textarea(name, value, rows) %}
|
|
||||||
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% import _self as forms %}
|
|
||||||
|
|
||||||
<p>{{ forms.textarea('comment') }}</p>
|
|
||||||
|
|
||||||
.. seealso:: :doc:`macro<../tags/macro>`, :doc:`from<../tags/from>`
|
|
@ -1,86 +0,0 @@
|
|||||||
``include``
|
|
||||||
===========
|
|
||||||
|
|
||||||
The ``include`` statement includes a template and return the rendered content
|
|
||||||
of that file into the current namespace:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% include 'header.html' %}
|
|
||||||
Body
|
|
||||||
{% include 'footer.html' %}
|
|
||||||
|
|
||||||
Included templates have access to the variables of the active context.
|
|
||||||
|
|
||||||
If you are using the filesystem loader, the templates are looked for in the
|
|
||||||
paths defined by it.
|
|
||||||
|
|
||||||
You can add additional variables by passing them after the ``with`` keyword:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# the foo template will have access to the variables from the current context and the foo one #}
|
|
||||||
{% include 'foo' with {'foo': 'bar'} %}
|
|
||||||
|
|
||||||
{% set vars = {'foo': 'bar'} %}
|
|
||||||
{% include 'foo' with vars %}
|
|
||||||
|
|
||||||
You can disable access to the context by appending the ``only`` keyword:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# only the foo variable will be accessible #}
|
|
||||||
{% include 'foo' with {'foo': 'bar'} only %}
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# no variable will be accessible #}
|
|
||||||
{% include 'foo' only %}
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
When including a template created by an end user, you should consider
|
|
||||||
sandboxing it. More information in the :doc:`Twig for Developers<../api>`
|
|
||||||
chapter.
|
|
||||||
|
|
||||||
The template name can be any valid Twig expression:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% include some_var %}
|
|
||||||
{% include ajax ? 'ajax.html' : 'not_ajax.html' %}
|
|
||||||
|
|
||||||
And if the expression evaluates to a ``Twig_Template`` object, Twig will use it
|
|
||||||
directly::
|
|
||||||
|
|
||||||
// {% include template %}
|
|
||||||
|
|
||||||
$template = $twig->loadTemplate('some_template.twig');
|
|
||||||
|
|
||||||
$twig->loadTemplate('template.twig')->display(array('template' => $template));
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
The ``ignore missing`` feature has been added in Twig 1.2.
|
|
||||||
|
|
||||||
You can mark an include with ``ignore missing`` in which case Twig will ignore
|
|
||||||
the statement if the template to be ignored does not exist. It has to be
|
|
||||||
placed just after the template name. Here some valid examples:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% include "sidebar.html" ignore missing %}
|
|
||||||
{% include "sidebar.html" ignore missing with {'foo': 'bar} %}
|
|
||||||
{% include "sidebar.html" ignore missing only %}
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
The possibility to pass an array of templates has been added in Twig 1.2.
|
|
||||||
|
|
||||||
You can also provide a list of templates that are checked for existence before
|
|
||||||
inclusion. The first template that exists will be included:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% include ['page_detailed.html', 'page.html'] %}
|
|
||||||
|
|
||||||
If ``ignore missing`` is given, it will fall back to rendering nothing if none
|
|
||||||
of the templates exist, otherwise it will throw an exception.
|
|
@ -1,22 +0,0 @@
|
|||||||
Tags
|
|
||||||
====
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
for
|
|
||||||
if
|
|
||||||
macro
|
|
||||||
filter
|
|
||||||
set
|
|
||||||
extends
|
|
||||||
block
|
|
||||||
include
|
|
||||||
import
|
|
||||||
from
|
|
||||||
use
|
|
||||||
spaceless
|
|
||||||
autoescape
|
|
||||||
raw
|
|
||||||
flush
|
|
||||||
do
|
|
@ -1,91 +0,0 @@
|
|||||||
``macro``
|
|
||||||
=========
|
|
||||||
|
|
||||||
Macros are comparable with functions in regular programming languages. They
|
|
||||||
are useful to put often used HTML idioms into reusable elements to not repeat
|
|
||||||
yourself.
|
|
||||||
|
|
||||||
Here is a small example of a macro that renders a form element:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% macro input(name, value, type, size) %}
|
|
||||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
Macros differs from native PHP functions in a few ways:
|
|
||||||
|
|
||||||
* Default argument values are defined by using the ``default`` filter in the
|
|
||||||
macro body;
|
|
||||||
|
|
||||||
* Arguments of a macro are always optional.
|
|
||||||
|
|
||||||
But as PHP functions, macros don't have access to the current template
|
|
||||||
variables.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
You can pass the whole context as an argument by using the special
|
|
||||||
``_context`` variable.
|
|
||||||
|
|
||||||
Macros can be defined in any template, and need to be "imported" before being
|
|
||||||
used (see the documentation for the :doc:`import<../tags/import>` tag for more
|
|
||||||
information):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% import "forms.html" as forms %}
|
|
||||||
|
|
||||||
The above ``import`` call imports the "forms.html" file (which can contain only
|
|
||||||
macros, or a template and some macros), and import the functions as items of
|
|
||||||
the ``forms`` variable.
|
|
||||||
|
|
||||||
The macro can then be called at will:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<p>{{ forms.input('username') }}</p>
|
|
||||||
<p>{{ forms.input('password', null, 'password') }}</p>
|
|
||||||
|
|
||||||
If macros are defined and used in the same template, you can use the
|
|
||||||
special ``_self`` variable, without importing them:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<p>{{ _self.input('username') }}</p>
|
|
||||||
|
|
||||||
When you want to use a macro in another one from the same file, use the ``_self``
|
|
||||||
variable:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% macro input(name, value, type, size) %}
|
|
||||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro wrapped_input(name, value, type, size) %}
|
|
||||||
<div class="field">
|
|
||||||
{{ _self.input(name, value, type, size) }}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
When the macro is defined in another file, you need to import it:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# forms.html #}
|
|
||||||
|
|
||||||
{% macro input(name, value, type, size) %}
|
|
||||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{# shortcuts.html #}
|
|
||||||
|
|
||||||
{% macro wrapped_input(name, value, type, size) %}
|
|
||||||
{% import "forms.html" as forms %}
|
|
||||||
<div class="field">
|
|
||||||
{{ forms.input(name, value, type, size) }}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
.. seealso:: :doc:`from<../tags/from>`, :doc:`import<../tags/import>`
|
|
@ -1,16 +0,0 @@
|
|||||||
``raw``
|
|
||||||
=======
|
|
||||||
|
|
||||||
The ``raw`` tag marks sections as being raw text that should not be parsed.
|
|
||||||
For example to put Twig syntax as example into a template you can use this
|
|
||||||
snippet:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% raw %}
|
|
||||||
<ul>
|
|
||||||
{% for item in seq %}
|
|
||||||
<li>{{ item }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endraw %}
|
|
@ -1,32 +0,0 @@
|
|||||||
``set``
|
|
||||||
=======
|
|
||||||
|
|
||||||
Inside code blocks you can also assign values to variables. Assignments use
|
|
||||||
the ``set`` tag and can have multiple targets:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set foo = 'foo' %}
|
|
||||||
|
|
||||||
{% set foo = [1, 2] %}
|
|
||||||
|
|
||||||
{% set foo = {'foo': 'bar'} %}
|
|
||||||
|
|
||||||
{% set foo = 'foo' ~ 'bar' %}
|
|
||||||
|
|
||||||
{% set foo, bar = 'foo', 'bar' %}
|
|
||||||
|
|
||||||
The ``set`` tag can also be used to 'capture' chunks of text:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set foo %}
|
|
||||||
<div id="pagination">
|
|
||||||
...
|
|
||||||
</div>
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
.. caution::
|
|
||||||
|
|
||||||
If you enable automatic output escaping, Twig will only consider the
|
|
||||||
content to be safe when capturing chunks of text.
|
|
@ -1,37 +0,0 @@
|
|||||||
``spaceless``
|
|
||||||
=============
|
|
||||||
|
|
||||||
Use the ``spaceless`` tag to remove whitespace *between HTML tags*, not
|
|
||||||
whitespace within HTML tags or whitespace in plain text:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% spaceless %}
|
|
||||||
<div>
|
|
||||||
<strong>foo</strong>
|
|
||||||
</div>
|
|
||||||
{% endspaceless %}
|
|
||||||
|
|
||||||
{# output will be <div><strong>foo</strong></div> #}
|
|
||||||
|
|
||||||
This tag is not meant to "optimize" the size of the generated HTML content but
|
|
||||||
merely to avoid extra whitespace between HTML tags to avoid browser rendering
|
|
||||||
quirks under some circumstances.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
If you want to optimize the size of the generated HTML content, gzip
|
|
||||||
compress the output instead.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
If you want to create a tag that actually removes all extra whitespace in
|
|
||||||
an HTML string, be warned that this is not as easy as it seems to be
|
|
||||||
(think of ``textarea`` or ``pre`` tags for instance). Using a third-party
|
|
||||||
library like Tidy is probably a better idea.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
For more information on whitespace control, read the
|
|
||||||
:doc:`dedicated<../templates>` section of the documentation and learn how
|
|
||||||
you can also use the whitespace control modifier on your tags.
|
|
@ -1,123 +0,0 @@
|
|||||||
``use``
|
|
||||||
=======
|
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
|
||||||
Horizontal reuse was added in Twig 1.1.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Horizontal reuse is an advanced Twig feature that is hardly ever needed in
|
|
||||||
regular templates. It is mainly used by projects that need to make
|
|
||||||
template blocks reusable without using inheritance.
|
|
||||||
|
|
||||||
Template inheritance is one of the most powerful Twig's feature but it is
|
|
||||||
limited to single inheritance; a template can only extend one other template.
|
|
||||||
This limitation makes template inheritance simple to understand and easy to
|
|
||||||
debug:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
|
|
||||||
Horizontal reuse is a way to achieve the same goal as multiple inheritance,
|
|
||||||
but without the associated complexity:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% use "blocks.html" %}
|
|
||||||
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
|
|
||||||
The ``use`` statement tells Twig to import the blocks defined in
|
|
||||||
```blocks.html`` into the current template (it's like macros, but for blocks):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
# blocks.html
|
|
||||||
{% block sidebar %}{% endblock %}
|
|
||||||
|
|
||||||
In this example, the ``use`` statement imports the ``sidebar`` block into the
|
|
||||||
main template. The code is mostly equivalent to the following one (the
|
|
||||||
imported blocks are not outputted automatically):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block sidebar %}{% endblock %}
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The ``use`` tag only imports a template if it does not extend another
|
|
||||||
template, if it does not define macros, and if the body is empty. But it
|
|
||||||
can *use* other templates.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Because ``use`` statements are resolved independently of the context
|
|
||||||
passed to the template, the template reference cannot be an expression.
|
|
||||||
|
|
||||||
The main template can also override any imported block. If the template
|
|
||||||
already defines the ``sidebar`` block, then the one defined in ``blocks.html``
|
|
||||||
is ignored. To avoid name conflicts, you can rename imported blocks:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% use "blocks.html" with sidebar as base_sidebar %}
|
|
||||||
|
|
||||||
{% block sidebar %}{% endblock %}
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
|
||||||
The ``parent()`` support was added in Twig 1.3.
|
|
||||||
|
|
||||||
The ``parent()`` function automatically determines the correct inheritance
|
|
||||||
tree, so it can be used when overriding a block defined in an imported
|
|
||||||
template:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% use "blocks.html" %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{{ parent() }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
|
|
||||||
In this example, ``parent()`` will correctly call the ``sidebar`` block from
|
|
||||||
the ``blocks.html`` template.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
In Twig 1.2, renaming allows you to simulate inheritance by calling the
|
|
||||||
"parent" block:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% use "blocks.html" with sidebar as parent_sidebar %}
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
{{ block('parent_sidebar') }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
You can use as many ``use`` statements as you want in any given template.
|
|
||||||
If two imported templates define the same block, the latest one wins.
|
|
@ -1,719 +0,0 @@
|
|||||||
Twig for Template Designers
|
|
||||||
===========================
|
|
||||||
|
|
||||||
This document describes the syntax and semantics of the template engine and
|
|
||||||
will be most useful as reference to those creating Twig templates.
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
|
|
||||||
A template is simply a text file. It can generate any text-based format (HTML,
|
|
||||||
XML, CSV, LaTeX, etc.). It doesn't have a specific extension, ``.html`` or
|
|
||||||
``.xml`` are just fine.
|
|
||||||
|
|
||||||
A template contains **variables** or **expressions**, which get replaced with
|
|
||||||
values when the template is evaluated, and **tags**, which control the logic
|
|
||||||
of the template.
|
|
||||||
|
|
||||||
Below is a minimal template that illustrates a few basics. We will cover the
|
|
||||||
details later on:
|
|
||||||
|
|
||||||
.. code-block:: html+jinja
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>My Webpage</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ul id="navigation">
|
|
||||||
{% for item in navigation %}
|
|
||||||
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h1>My Webpage</h1>
|
|
||||||
{{ a_variable }}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
There are two kinds of delimiters: ``{% ... %}`` and ``{{ ... }}``. The first
|
|
||||||
one is used to execute statements such as for-loops, the latter prints the
|
|
||||||
result of an expression to the template.
|
|
||||||
|
|
||||||
IDEs Integration
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Many IDEs support syntax highlighting and auto-completion for Twig:
|
|
||||||
|
|
||||||
* *Textmate* via the `Twig bundle`_
|
|
||||||
* *Vim* via the `Jinja syntax plugin`_
|
|
||||||
* *Netbeans* via the `Twig syntax plugin`_
|
|
||||||
* *PhpStorm* (native as of 2.1)
|
|
||||||
* *Eclipse* via the `Twig plugin`_
|
|
||||||
* *Sublime Text* via the `Twig bundle`_
|
|
||||||
* *GtkSourceView* via the `Twig language definition`_ (used by gedit and other projects)
|
|
||||||
* *Coda* and *SubEthaEdit* via the `Twig syntax mode`_
|
|
||||||
|
|
||||||
Variables
|
|
||||||
---------
|
|
||||||
|
|
||||||
The application passes variables to the templates you can mess around in the
|
|
||||||
template. Variables may have attributes or elements on them you can access
|
|
||||||
too. How a variable looks like heavily depends on the application providing
|
|
||||||
those.
|
|
||||||
|
|
||||||
You can use a dot (``.``) to access attributes of a variable (methods or
|
|
||||||
properties of a PHP object, or items of a PHP array), or the so-called
|
|
||||||
"subscript" syntax (``[]``):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ foo.bar }}
|
|
||||||
{{ foo['bar'] }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
It's important to know that the curly braces are *not* part of the
|
|
||||||
variable but the print statement. If you access variables inside tags
|
|
||||||
don't put the braces around.
|
|
||||||
|
|
||||||
If a variable or attribute does not exist you will get back a ``null`` value.
|
|
||||||
|
|
||||||
.. sidebar:: Implementation
|
|
||||||
|
|
||||||
For convenience sake ``foo.bar`` does the following things on the PHP
|
|
||||||
layer:
|
|
||||||
|
|
||||||
* check if ``foo`` is an array and ``bar`` a valid element;
|
|
||||||
* if not, and if ``foo`` is an object, check that ``bar`` is a valid property;
|
|
||||||
* if not, and if ``foo`` is an object, check that ``bar`` is a valid method
|
|
||||||
(even if ``bar`` is the constructor - use ``__construct()`` instead);
|
|
||||||
* if not, and if ``foo`` is an object, check that ``getBar`` is a valid method;
|
|
||||||
* if not, and if ``foo`` is an object, check that ``isBar`` is a valid method;
|
|
||||||
* if not, return a ``null`` value.
|
|
||||||
|
|
||||||
``foo['bar']`` on the other hand only works with PHP arrays:
|
|
||||||
|
|
||||||
* check if ``foo`` is an array and ``bar`` a valid element;
|
|
||||||
* if not, return a ``null`` value.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you want to get a dynamic attribute on a variable, use the
|
|
||||||
:doc:`attribute<functions/attribute>` function instead.
|
|
||||||
|
|
||||||
Global Variables
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The following variables are always available in templates:
|
|
||||||
|
|
||||||
* ``_self``: references the current template;
|
|
||||||
* ``_context``: references the current context;
|
|
||||||
* ``_charset``: references the current charset.
|
|
||||||
|
|
||||||
Setting Variables
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You can assign values to variables inside code blocks. Assignments use the
|
|
||||||
:doc:`set<tags/set>` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set foo = 'foo' %}
|
|
||||||
{% set foo = [1, 2] %}
|
|
||||||
{% set foo = {'foo': 'bar'} %}
|
|
||||||
|
|
||||||
Filters
|
|
||||||
-------
|
|
||||||
|
|
||||||
Variables can be modified by **filters**. Filters are separated from the
|
|
||||||
variable by a pipe symbol (``|``) and may have optional arguments in
|
|
||||||
parentheses. Multiple filters can be chained. The output of one filter is
|
|
||||||
applied to the next.
|
|
||||||
|
|
||||||
The following example removes all HTML tags from the ``name`` and title-cases
|
|
||||||
it:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ name|striptags|title }}
|
|
||||||
|
|
||||||
Filters that accept arguments have parentheses around the arguments. This
|
|
||||||
example will join a list by commas:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ list|join(', ') }}
|
|
||||||
|
|
||||||
To apply a filter on a section of code, wrap it with the
|
|
||||||
:doc:`filter<tags/filter>` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% filter upper %}
|
|
||||||
This text becomes uppercase
|
|
||||||
{% endfilter %}
|
|
||||||
|
|
||||||
Go to the :doc:`filters<filters/index>` page to learn more about the built-in
|
|
||||||
filters.
|
|
||||||
|
|
||||||
Functions
|
|
||||||
---------
|
|
||||||
|
|
||||||
Functions can be called to generate content. Functions are called by their
|
|
||||||
name followed by parentheses (``()``) and may have arguments.
|
|
||||||
|
|
||||||
For instance, the ``range`` function returns a list containing an arithmetic
|
|
||||||
progression of integers:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for i in range(0, 3) %}
|
|
||||||
{{ i }},
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
Go to the :doc:`functions<functions/index>` page to learn more about the
|
|
||||||
built-in functions.
|
|
||||||
|
|
||||||
Control Structure
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
A control structure refers to all those things that control the flow of a
|
|
||||||
program - conditionals (i.e. ``if``/``elseif``/``else``), ``for``-loops, as
|
|
||||||
well as things like blocks. Control structures appear inside ``{% ... %}``
|
|
||||||
blocks.
|
|
||||||
|
|
||||||
For example, to display a list of users provided in a variable called
|
|
||||||
``users``, use the :doc:`for<tags/for>` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
<h1>Members</h1>
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li>{{ user.username|e }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
The :doc:`if<tags/if>` tag can be used to test an expression:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if users|length > 0 %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li>{{ user.username|e }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
Go to the :doc:`tags<tags/index>` page to learn more about the built-in tags.
|
|
||||||
|
|
||||||
Comments
|
|
||||||
--------
|
|
||||||
|
|
||||||
To comment-out part of a line in a template, use the comment syntax ``{# ...
|
|
||||||
#}``. This is useful for debugging or to add information for other template
|
|
||||||
designers or yourself:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# note: disabled template because we no longer use this
|
|
||||||
{% for user in users %}
|
|
||||||
...
|
|
||||||
{% endfor %}
|
|
||||||
#}
|
|
||||||
|
|
||||||
Including other Templates
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
The :doc:`include<tags/include>` tag is useful to include a template and
|
|
||||||
return the rendered content of that template into the current one:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% include 'sidebar.html' %}
|
|
||||||
|
|
||||||
Per default included templates are passed the current context.
|
|
||||||
|
|
||||||
The context that is passed to the included template includes variables defined
|
|
||||||
in the template:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% for box in boxes %}
|
|
||||||
{% include "render_box.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
The included template ``render_box.html`` is able to access ``box``.
|
|
||||||
|
|
||||||
The filename of the template depends on the template loader. For instance, the
|
|
||||||
``Twig_Loader_Filesystem`` allows you to access other templates by giving the
|
|
||||||
filename. You can access templates in subdirectories with a slash:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% include "sections/articles/sidebar.html" %}
|
|
||||||
|
|
||||||
This behavior depends on the application embedding Twig.
|
|
||||||
|
|
||||||
Template Inheritance
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
The most powerful part of Twig is template inheritance. Template inheritance
|
|
||||||
allows you to build a base "skeleton" template that contains all the common
|
|
||||||
elements of your site and defines **blocks** that child templates can
|
|
||||||
override.
|
|
||||||
|
|
||||||
Sounds complicated but is very basic. It's easiest to understand it by
|
|
||||||
starting with an example.
|
|
||||||
|
|
||||||
Let's define a base template, ``base.html``, which defines a simple HTML
|
|
||||||
skeleton document that you might use for a simple two-column page:
|
|
||||||
|
|
||||||
.. code-block:: html+jinja
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
{% block head %}
|
|
||||||
<link rel="stylesheet" href="style.css" />
|
|
||||||
<title>{% block title %}{% endblock %} - My Webpage</title>
|
|
||||||
{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content">{% block content %}{% endblock %}</div>
|
|
||||||
<div id="footer">
|
|
||||||
{% block footer %}
|
|
||||||
© Copyright 2011 by <a href="http://domain.invalid/">you</a>.
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
In this example, the :doc:`block<tags/block>` tags define four blocks that
|
|
||||||
child templates can fill in. All the ``block`` tag does is to tell the
|
|
||||||
template engine that a child template may override those portions of the
|
|
||||||
template.
|
|
||||||
|
|
||||||
A child template might look like this:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Index{% endblock %}
|
|
||||||
{% block head %}
|
|
||||||
{{ parent() }}
|
|
||||||
<style type="text/css">
|
|
||||||
.important { color: #336699; }
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<h1>Index</h1>
|
|
||||||
<p class="important">
|
|
||||||
Welcome on my awesome homepage.
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
The :doc:`extends<tags/extends>` tag is the key here. It tells the template
|
|
||||||
engine that this template "extends" another template. When the template system
|
|
||||||
evaluates this template, first it locates the parent. The extends tag should
|
|
||||||
be the first tag in the template.
|
|
||||||
|
|
||||||
Note that since the child template doesn't define the ``footer`` block, the
|
|
||||||
value from the parent template is used instead.
|
|
||||||
|
|
||||||
It's possible to render the contents of the parent block by using the
|
|
||||||
:doc:`parent<functions/parent>` function. This gives back the results of the
|
|
||||||
parent block:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% block sidebar %}
|
|
||||||
<h3>Table Of Contents</h3>
|
|
||||||
...
|
|
||||||
{{ parent() }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
The documentation page for the :doc:`extends<tags/extends>` tag describes
|
|
||||||
more advanced features like block nesting, scope, dynamic inheritance, and
|
|
||||||
conditional inheritance.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Twig also supports multiple inheritance with the so called horizontal reuse
|
|
||||||
with the help of the :doc:`use<tags/use>` tag. This is an advanced feature
|
|
||||||
hardly ever needed in regular templates.
|
|
||||||
|
|
||||||
HTML Escaping
|
|
||||||
-------------
|
|
||||||
|
|
||||||
When generating HTML from templates, there's always a risk that a variable
|
|
||||||
will include characters that affect the resulting HTML. There are two
|
|
||||||
approaches: manually escaping each variable or automatically escaping
|
|
||||||
everything by default.
|
|
||||||
|
|
||||||
Twig supports both, automatic escaping is enabled by default.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Automatic escaping is only supported if the *escaper* extension has been
|
|
||||||
enabled (which is the default).
|
|
||||||
|
|
||||||
Working with Manual Escaping
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If manual escaping is enabled it's **your** responsibility to escape variables
|
|
||||||
if needed. What to escape? If you have a variable that *may* include any of
|
|
||||||
the following chars (``>``, ``<``, ``&``, or ``"``) you **have to** escape it
|
|
||||||
unless the variable contains well-formed and trusted HTML. Escaping works by
|
|
||||||
piping the variable through the :doc:`escape<filters/escape>` or ``e`` filter:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ user.username|e }}
|
|
||||||
{{ user.username|e('js') }}
|
|
||||||
|
|
||||||
Working with Automatic Escaping
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Whether automatic escaping is enabled or not, you can mark a section of a
|
|
||||||
template to be escaped or not by using the :doc:`autoescape<tags/autoescape>`
|
|
||||||
tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% autoescape true %}
|
|
||||||
Everything will be automatically escaped in this block
|
|
||||||
{% endautoescape %}
|
|
||||||
|
|
||||||
Escaping
|
|
||||||
--------
|
|
||||||
|
|
||||||
It is sometimes desirable or even necessary to have Twig ignore parts it would
|
|
||||||
otherwise handle as variables or blocks. For example if the default syntax is
|
|
||||||
used and you want to use ``{{`` as raw string in the template and not start a
|
|
||||||
variable you have to use a trick.
|
|
||||||
|
|
||||||
The easiest way is to output the variable delimiter (``{{``) by using a variable
|
|
||||||
expression:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ '{{' }}
|
|
||||||
|
|
||||||
For bigger sections it makes sense to mark a block :doc:`raw<tags/raw>`.
|
|
||||||
|
|
||||||
Macros
|
|
||||||
------
|
|
||||||
|
|
||||||
Macros are comparable with functions in regular programming languages. They
|
|
||||||
are useful to put often used HTML idioms into reusable elements to not repeat
|
|
||||||
yourself.
|
|
||||||
|
|
||||||
A macro is defined via the :doc:`macro<tags/macro>` tag. Here is a small
|
|
||||||
example of a macro that renders a form element:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% macro input(name, value, type, size) %}
|
|
||||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
Macros can be defined in any template, and need to be "imported" before being
|
|
||||||
used via the :doc:`import<tags/import>` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% import "forms.html" as forms %}
|
|
||||||
|
|
||||||
<p>{{ forms.input('username') }}</p>
|
|
||||||
|
|
||||||
Alternatively you can import names from the template into the current
|
|
||||||
namespace via the :doc:`from<tags/from>` tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% from 'forms.html' import input as input_field, textarea %}
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>Username</dt>
|
|
||||||
<dd>{{ input_field('username') }}</dd>
|
|
||||||
<dt>Password</dt>
|
|
||||||
<dd>{{ input_field('password', type='password') }}</dd>
|
|
||||||
</dl>
|
|
||||||
<p>{{ textarea('comment') }}</p>
|
|
||||||
|
|
||||||
Expressions
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Twig allows expressions everywhere. These work very similar to regular PHP and
|
|
||||||
even if you're not working with PHP you should feel comfortable with it.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The operator precedence is as follows, with the lowest-precedence
|
|
||||||
operators listed first: ``&``, ``^``, ``|``, ``or``, ``and``, ``==``,
|
|
||||||
``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``..``, ``+``, ``-``, ``~``,
|
|
||||||
``*``, ``/``, ``//``, ``%``, ``is``, and ``**``.
|
|
||||||
|
|
||||||
Literals
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
Support for hash keys as names and expressions was added in Twig 1.5.
|
|
||||||
|
|
||||||
The simplest form of expressions are literals. Literals are representations
|
|
||||||
for PHP types such as strings, numbers, and arrays. The following literals
|
|
||||||
exist:
|
|
||||||
|
|
||||||
* ``"Hello World"``: Everything between two double or single quotes is a
|
|
||||||
string. They are useful whenever you need a string in the template (for
|
|
||||||
example as arguments to function calls, filters or just to extend or
|
|
||||||
include a template).
|
|
||||||
|
|
||||||
* ``42`` / ``42.23``: Integers and floating point numbers are created by just
|
|
||||||
writing the number down. If a dot is present the number is a float,
|
|
||||||
otherwise an integer.
|
|
||||||
|
|
||||||
* ``["foo", "bar"]``: Arrays are defined by a sequence of expressions
|
|
||||||
separated by a comma (``,``) and wrapped with squared brackets (``[]``).
|
|
||||||
|
|
||||||
* ``{"foo": "bar"}``: Hashes are defined by a list of keys and values
|
|
||||||
separated by a comma (``,``) and wrapped with curly braces (``{}``):
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# keys as string #}
|
|
||||||
{ 'foo': 'foo', 'bar': 'bar' }
|
|
||||||
|
|
||||||
{# keys as names (equivalent to the previous hash) -- as of Twig 1.5 #}
|
|
||||||
{ foo: 'foo', bar: 'bar' }
|
|
||||||
|
|
||||||
{# keys as integer #}
|
|
||||||
{ 2: 'foo', 4: 'bar' }
|
|
||||||
|
|
||||||
{# keys as expressions (the expression must be enclosed into parentheses) -- as of Twig 1.5 #}
|
|
||||||
{ (1 + 1): 'foo', (a ~ 'b'): 'bar' }
|
|
||||||
|
|
||||||
* ``true`` / ``false``: ``true`` represents the true value, ``false``
|
|
||||||
represents the false value.
|
|
||||||
|
|
||||||
* ``null``: ``null`` represents no specific value. This is the value returned
|
|
||||||
when a variable does not exist. ``none`` is an alias for ``null``.
|
|
||||||
|
|
||||||
Arrays and hashes can be nested:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set foo = [1, {"foo": "bar"}] %}
|
|
||||||
|
|
||||||
Math
|
|
||||||
~~~~
|
|
||||||
|
|
||||||
Twig allows you to calculate with values. This is rarely useful in templates
|
|
||||||
but exists for completeness' sake. The following operators are supported:
|
|
||||||
|
|
||||||
* ``+``: Adds two objects together (the operands are casted to numbers). ``{{
|
|
||||||
1 + 1 }}`` is ``2``.
|
|
||||||
|
|
||||||
* ``-``: Substracts the second number from the first one. ``{{ 3 - 2 }}`` is
|
|
||||||
``1``.
|
|
||||||
|
|
||||||
* ``/``: Divides two numbers. The return value will be a floating point
|
|
||||||
number. ``{{ 1 / 2 }}`` is ``{{ 0.5 }}``.
|
|
||||||
|
|
||||||
* ``%``: Calculates the remainder of an integer division. ``{{ 11 % 7 }}`` is
|
|
||||||
``4``.
|
|
||||||
|
|
||||||
* ``//``: Divides two numbers and returns the truncated integer result. ``{{
|
|
||||||
20 // 7 }}`` is ``2``.
|
|
||||||
|
|
||||||
* ``*``: Multiplies the left operand with the right one. ``{{ 2 * 2 }}`` would
|
|
||||||
return ``4``.
|
|
||||||
|
|
||||||
* ``**``: Raises the left operand to the power of the right operand. ``{{ 2 **
|
|
||||||
3 }}`` would return ``8``.
|
|
||||||
|
|
||||||
Logic
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
You can combine multiple expressions with the following operators:
|
|
||||||
|
|
||||||
* ``and``: Returns true if the left and the right operands are both true.
|
|
||||||
|
|
||||||
* ``or``: Returns true if the left or the right operand is true.
|
|
||||||
|
|
||||||
* ``not``: Negates a statement.
|
|
||||||
|
|
||||||
* ``(expr)``: Groups an expression.
|
|
||||||
|
|
||||||
Comparisons
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
The following comparison operators are supported in any expression: ``==``,
|
|
||||||
``!=``, ``<``, ``>``, ``>=``, and ``<=``.
|
|
||||||
|
|
||||||
Containment Operator
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``in`` operator performs containment test.
|
|
||||||
|
|
||||||
It returns ``true`` if the left operand is contained in the right:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# returns true #}
|
|
||||||
|
|
||||||
{{ 1 in [1, 2, 3] }}
|
|
||||||
|
|
||||||
{{ 'cd' in 'abcde' }}
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
You can use this filter to perform a containment test on strings, arrays,
|
|
||||||
or objects implementing the ``Traversable`` interface.
|
|
||||||
|
|
||||||
To perform a negative test, use the ``not in`` operator:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if 1 not in [1, 2, 3] %}
|
|
||||||
|
|
||||||
{# is equivalent to #}
|
|
||||||
{% if not (1 in [1, 2, 3]) %}
|
|
||||||
|
|
||||||
Test Operator
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The ``is`` operator performs tests. Tests can be used to test a variable against
|
|
||||||
a common expression. The right operand is name of the test:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# find out if a variable is odd #}
|
|
||||||
|
|
||||||
{{ name is odd }}
|
|
||||||
|
|
||||||
Tests can accept arguments too:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if loop.index is divisibleby(3) %}
|
|
||||||
|
|
||||||
Tests can be negated by using the ``is not`` operator:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if loop.index is not divisibleby(3) %}
|
|
||||||
|
|
||||||
{# is equivalent to #}
|
|
||||||
{% if not (loop.index is divisibleby(3)) %}
|
|
||||||
|
|
||||||
Go to the :doc:`tests<tests/index>` page to learn more about the built-in
|
|
||||||
tests.
|
|
||||||
|
|
||||||
Other Operators
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The following operators are very useful but don't fit into any of the other
|
|
||||||
categories:
|
|
||||||
|
|
||||||
* ``..``: Creates a sequence based on the operand before and after the
|
|
||||||
operator (this is just syntactic sugar for the :doc:`range<functions/range>`
|
|
||||||
function).
|
|
||||||
|
|
||||||
* ``|``: Applies a filter.
|
|
||||||
|
|
||||||
* ``~``: Converts all operands into strings and concatenates them. ``{{ "Hello
|
|
||||||
" ~ name ~ "!" }}`` would return (assuming ``name`` is ``'John'``) ``Hello
|
|
||||||
John!``.
|
|
||||||
|
|
||||||
* ``.``, ``[]``: Gets an attribute of an object.
|
|
||||||
|
|
||||||
* ``?:``: The PHP ternary operator: ``{{ foo ? 'yes' : 'no' }}``
|
|
||||||
|
|
||||||
String Interpolation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
String interpolation was added in Twig 1.5.
|
|
||||||
|
|
||||||
String interpolation (`#{expression}`) allows any valid expression to appear
|
|
||||||
within a string. The result of evaluating that expression is inserted into the
|
|
||||||
string:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ "foo #{bar} baz" }}
|
|
||||||
{{ "foo #{1 + 2} baz" }}
|
|
||||||
|
|
||||||
Whitespace Control
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
|
||||||
Tag level whitespace control was added in Twig 1.1.
|
|
||||||
|
|
||||||
The first newline after a template tag is removed automatically (like in PHP.)
|
|
||||||
Whitespace is not further modified by the template engine, so each whitespace
|
|
||||||
(spaces, tabs, newlines etc.) is returned unchanged.
|
|
||||||
|
|
||||||
Use the ``spaceless`` tag to remove whitespace *between HTML tags*:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% spaceless %}
|
|
||||||
<div>
|
|
||||||
<strong>foo</strong>
|
|
||||||
</div>
|
|
||||||
{% endspaceless %}
|
|
||||||
|
|
||||||
{# output will be <div><strong>foo</strong></div> #}
|
|
||||||
|
|
||||||
In addition to the spaceless tag you can also control whitespace on a per tag
|
|
||||||
level. By using the whitespace control modifier on your tags, you can trim
|
|
||||||
leading and or trailing whitespace:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set value = 'no spaces' %}
|
|
||||||
{#- No leading/trailing whitespace -#}
|
|
||||||
{%- if true -%}
|
|
||||||
{{- value -}}
|
|
||||||
{%- endif -%}
|
|
||||||
|
|
||||||
{# output 'no spaces' #}
|
|
||||||
|
|
||||||
The above sample shows the default whitespace control modifier, and how you can
|
|
||||||
use it to remove whitespace around tags. Trimming space will consume all whitespace
|
|
||||||
for that side of the tag. It is possible to use whitespace trimming on one side
|
|
||||||
of a tag:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% set value = 'no spaces' %}
|
|
||||||
<li> {{- value }} </li>
|
|
||||||
|
|
||||||
{# outputs '<li>no spaces </li>' #}
|
|
||||||
|
|
||||||
Extensions
|
|
||||||
----------
|
|
||||||
|
|
||||||
Twig can be easily extended.
|
|
||||||
|
|
||||||
If you are looking for new tags, filters, or functions, have a look at the Twig official
|
|
||||||
`extension repository`_.
|
|
||||||
|
|
||||||
If you want to create your own, read :doc:`extensions`.
|
|
||||||
|
|
||||||
.. _`Twig bundle`: https://github.com/Anomareh/PHP-Twig.tmbundle
|
|
||||||
.. _`Jinja syntax plugin`: http://jinja.pocoo.org/2/documentation/integration
|
|
||||||
.. _`Twig syntax plugin`: http://plugins.netbeans.org/plugin/37069/php-twig
|
|
||||||
.. _`Twig plugin`: https://github.com/pulse00/Twig-Eclipse-Plugin
|
|
||||||
.. _`Twig language definition`: https://github.com/gabrielcorpse/gedit-twig-template-language
|
|
||||||
.. _`extension repository`: http://github.com/fabpot/Twig-extensions
|
|
||||||
.. _`Twig syntax mode`: https://github.com/bobthecow/Twig-HTML.mode
|
|
@ -1,11 +0,0 @@
|
|||||||
``constant``
|
|
||||||
============
|
|
||||||
|
|
||||||
``constant`` checks if a variable has the exact same value as a constant. You
|
|
||||||
can use either global constants or class constants:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if post.status is constant('Post::PUBLISHED') %}
|
|
||||||
the status attribute is exactly the same as Post::PUBLISHED
|
|
||||||
{% endif %}
|
|
@ -1,30 +0,0 @@
|
|||||||
``defined``
|
|
||||||
===========
|
|
||||||
|
|
||||||
``defined`` checks if a variable is defined in the current context. This is very
|
|
||||||
useful if you use the ``strict_variables`` option:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# defined works with variable names #}
|
|
||||||
{% if foo is defined %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# and attributes on variables names #}
|
|
||||||
{% if foo.bar is defined %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if foo['bar'] is defined %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
When using the ``defined`` test on an expression that uses variables in some
|
|
||||||
method calls, be sure that they are all defined first:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if var is defined and foo.method(var) is defined %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
@ -1,10 +0,0 @@
|
|||||||
``divisibleby``
|
|
||||||
===============
|
|
||||||
|
|
||||||
``divisibleby`` checks if a variable is divisible by a number:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if loop.index is divisibleby(3) %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
@ -1,11 +0,0 @@
|
|||||||
``empty``
|
|
||||||
=========
|
|
||||||
|
|
||||||
``empty`` checks if a variable is empty:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{# evaluates to true if the foo variable is null, false, or the empty string #}
|
|
||||||
{% if foo is empty %}
|
|
||||||
...
|
|
||||||
{% endif %}
|
|
@ -1,10 +0,0 @@
|
|||||||
``even``
|
|
||||||
========
|
|
||||||
|
|
||||||
``even`` returns ``true`` if the given number is even:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var is even }}
|
|
||||||
|
|
||||||
.. seealso:: :doc:`odd<../tests/odd>`
|
|
@ -1,14 +0,0 @@
|
|||||||
Tests
|
|
||||||
=====
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
divisibleby
|
|
||||||
null
|
|
||||||
even
|
|
||||||
odd
|
|
||||||
sameas
|
|
||||||
constant
|
|
||||||
defined
|
|
||||||
empty
|
|
@ -1,12 +0,0 @@
|
|||||||
``null``
|
|
||||||
========
|
|
||||||
|
|
||||||
``null`` returns ``true`` if the variable is ``null``:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var is null }}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
``none`` is an alias for ``null``.
|
|
@ -1,10 +0,0 @@
|
|||||||
``odd``
|
|
||||||
=======
|
|
||||||
|
|
||||||
``odd`` returns ``true`` if the given number is odd:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{{ var is odd }}
|
|
||||||
|
|
||||||
.. seealso:: :doc:`even<../tests/even>`
|
|
@ -1,11 +0,0 @@
|
|||||||
``sameas``
|
|
||||||
==========
|
|
||||||
|
|
||||||
``sameas`` checks if a variable points to the same memory address than another
|
|
||||||
variable:
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% if foo.attribute is sameas(false) %}
|
|
||||||
the foo attribute really is the ``false`` PHP value
|
|
||||||
{% endif %}
|
|
30
lib/twig/ext/twig/.gitignore
vendored
30
lib/twig/ext/twig/.gitignore
vendored
@ -1,30 +0,0 @@
|
|||||||
*.sw*
|
|
||||||
.deps
|
|
||||||
Makefile
|
|
||||||
Makefile.fragments
|
|
||||||
Makefile.global
|
|
||||||
Makefile.objects
|
|
||||||
acinclude.m4
|
|
||||||
aclocal.m4
|
|
||||||
build/
|
|
||||||
config.cache
|
|
||||||
config.guess
|
|
||||||
config.h
|
|
||||||
config.h.in
|
|
||||||
config.log
|
|
||||||
config.nice
|
|
||||||
config.status
|
|
||||||
config.sub
|
|
||||||
configure
|
|
||||||
configure.in
|
|
||||||
install-sh
|
|
||||||
libtool
|
|
||||||
ltmain.sh
|
|
||||||
missing
|
|
||||||
mkinstalldirs
|
|
||||||
run-tests.php
|
|
||||||
twig.loT
|
|
||||||
.libs/
|
|
||||||
modules/
|
|
||||||
twig.la
|
|
||||||
twig.lo
|
|
@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2011, Derick Rethans <derick@derickrethans.nl>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,8 +0,0 @@
|
|||||||
dnl config.m4 for extension twig
|
|
||||||
|
|
||||||
PHP_ARG_ENABLE(twig, whether to enable twig support,
|
|
||||||
[ --enable-twig Enable twig support])
|
|
||||||
|
|
||||||
if test "$PHP_TWIG" != "no"; then
|
|
||||||
PHP_NEW_EXTENSION(twig, twig.c, $ext_shared)
|
|
||||||
fi
|
|
@ -1,8 +0,0 @@
|
|||||||
// vim:ft=javascript
|
|
||||||
|
|
||||||
ARG_ENABLE("twig", "Twig support", "no");
|
|
||||||
|
|
||||||
if (PHP_TWIG != "no") {
|
|
||||||
AC_DEFINE('HAVE_TWIG', 1);
|
|
||||||
EXTENSION('twig', 'twig.c');
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
+----------------------------------------------------------------------+
|
|
||||||
| Twig Extension |
|
|
||||||
+----------------------------------------------------------------------+
|
|
||||||
| Copyright (c) 2011 Derick Rethans |
|
|
||||||
+----------------------------------------------------------------------+
|
|
||||||
| Redistribution and use in source and binary forms, with or without |
|
|
||||||
| modification, are permitted provided that the conditions mentioned |
|
|
||||||
| in the accompanying LICENSE file are met (BSD, revised). |
|
|
||||||
+----------------------------------------------------------------------+
|
|
||||||
| Author: Derick Rethans <derick@derickrethans.nl> |
|
|
||||||
+----------------------------------------------------------------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PHP_TWIG_H
|
|
||||||
#define PHP_TWIG_H
|
|
||||||
|
|
||||||
#define PHP_TWIG_VERSION "1.6.4"
|
|
||||||
|
|
||||||
#include "php.h"
|
|
||||||
|
|
||||||
extern zend_module_entry twig_module_entry;
|
|
||||||
#define phpext_twig_ptr &twig_module_entry
|
|
||||||
|
|
||||||
#ifdef PHP_WIN32
|
|
||||||
#define PHP_TWIG_API __declspec(dllexport)
|
|
||||||
#else
|
|
||||||
#define PHP_TWIG_API
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ZTS
|
|
||||||
#include "TSRM.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PHP_FUNCTION(twig_template_get_attributes);
|
|
||||||
|
|
||||||
PHP_MINIT_FUNCTION(twig);
|
|
||||||
PHP_MSHUTDOWN_FUNCTION(twig);
|
|
||||||
PHP_RINIT_FUNCTION(twig);
|
|
||||||
PHP_RSHUTDOWN_FUNCTION(twig);
|
|
||||||
PHP_MINFO_FUNCTION(twig);
|
|
||||||
|
|
||||||
#ifdef ZTS
|
|
||||||
#define TWIG_G(v) TSRMG(twig_globals_id, zend_twig_globals *, v)
|
|
||||||
#else
|
|
||||||
#define TWIG_G(v) (twig_globals.v)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
File diff suppressed because it is too large
Load Diff
@ -1,176 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2009 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Twig base exception.
|
|
||||||
*
|
|
||||||
* @package twig
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class Twig_Error extends Exception
|
|
||||||
{
|
|
||||||
protected $lineno;
|
|
||||||
protected $filename;
|
|
||||||
protected $rawMessage;
|
|
||||||
protected $previous;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param string $message The error message
|
|
||||||
* @param integer $lineno The template line where the error occurred
|
|
||||||
* @param string $filename The template file name where the error occurred
|
|
||||||
* @param Exception $previous The previous exception
|
|
||||||
*/
|
|
||||||
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
|
|
||||||
{
|
|
||||||
if (-1 === $lineno || null === $filename) {
|
|
||||||
if ($trace = $this->getTemplateTrace()) {
|
|
||||||
if (-1 === $lineno) {
|
|
||||||
$lineno = $this->guessTemplateLine($trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $filename) {
|
|
||||||
$filename = $trace['object']->getTemplateName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->lineno = $lineno;
|
|
||||||
$this->filename = $filename;
|
|
||||||
$this->rawMessage = $message;
|
|
||||||
|
|
||||||
$this->updateRepr();
|
|
||||||
|
|
||||||
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
|
|
||||||
$this->previous = $previous;
|
|
||||||
parent::__construct($this->message);
|
|
||||||
} else {
|
|
||||||
parent::__construct($this->message, 0, $previous);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the raw message.
|
|
||||||
*
|
|
||||||
* @return string The raw message
|
|
||||||
*/
|
|
||||||
public function getRawMessage()
|
|
||||||
{
|
|
||||||
return $this->rawMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the filename where the error occurred.
|
|
||||||
*
|
|
||||||
* @return string The filename
|
|
||||||
*/
|
|
||||||
public function getTemplateFile()
|
|
||||||
{
|
|
||||||
return $this->filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the filename where the error occurred.
|
|
||||||
*
|
|
||||||
* @param string $filename The filename
|
|
||||||
*/
|
|
||||||
public function setTemplateFile($filename)
|
|
||||||
{
|
|
||||||
$this->filename = $filename;
|
|
||||||
|
|
||||||
$this->updateRepr();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the template line where the error occurred.
|
|
||||||
*
|
|
||||||
* @return integer The template line
|
|
||||||
*/
|
|
||||||
public function getTemplateLine()
|
|
||||||
{
|
|
||||||
return $this->lineno;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the template line where the error occurred.
|
|
||||||
*
|
|
||||||
* @param integer $lineno The template line
|
|
||||||
*/
|
|
||||||
public function setTemplateLine($lineno)
|
|
||||||
{
|
|
||||||
$this->lineno = $lineno;
|
|
||||||
|
|
||||||
$this->updateRepr();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For PHP < 5.3.0, provides access to the getPrevious() method.
|
|
||||||
*
|
|
||||||
* @param string $method The method name
|
|
||||||
* @param array $arguments The parameters to be passed to the method
|
|
||||||
*
|
|
||||||
* @return Exception The previous exception or null
|
|
||||||
*/
|
|
||||||
public function __call($method, $arguments)
|
|
||||||
{
|
|
||||||
if ('getprevious' == strtolower($method)) {
|
|
||||||
return $this->previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function updateRepr()
|
|
||||||
{
|
|
||||||
$this->message = $this->rawMessage;
|
|
||||||
|
|
||||||
$dot = false;
|
|
||||||
if ('.' === substr($this->message, -1)) {
|
|
||||||
$this->message = substr($this->message, 0, -1);
|
|
||||||
$dot = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $this->filename) {
|
|
||||||
$this->message .= sprintf(' in %s', is_string($this->filename) ? '"'.$this->filename.'"' : json_encode($this->filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->lineno >= 0) {
|
|
||||||
$this->message .= sprintf(' at line %d', $this->lineno);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($dot) {
|
|
||||||
$this->message .= '.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getTemplateTrace()
|
|
||||||
{
|
|
||||||
foreach (debug_backtrace() as $trace) {
|
|
||||||
if (isset($trace['object']) && $trace['object'] instanceof Twig_Template) {
|
|
||||||
return $trace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function guessTemplateLine($trace)
|
|
||||||
{
|
|
||||||
if (isset($trace['line'])) {
|
|
||||||
foreach ($trace['object']->getDebugInfo() as $codeLine => $templateLine) {
|
|
||||||
if ($codeLine <= $trace['line']) {
|
|
||||||
return $templateLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2010 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when an error occurs during template loading.
|
|
||||||
*
|
|
||||||
* @package twig
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class Twig_Error_Loader extends Twig_Error
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2010 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a template filter.
|
|
||||||
*
|
|
||||||
* @package twig
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
interface Twig_FilterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Compiles a filter.
|
|
||||||
*
|
|
||||||
* @return string The PHP code for the filter
|
|
||||||
*/
|
|
||||||
function compile();
|
|
||||||
|
|
||||||
function needsEnvironment();
|
|
||||||
|
|
||||||
function needsContext();
|
|
||||||
|
|
||||||
function getSafe(Twig_Node $filterArgs);
|
|
||||||
|
|
||||||
function getPreEscape();
|
|
||||||
|
|
||||||
function setArguments($arguments);
|
|
||||||
|
|
||||||
function getArguments();
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2011 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads templates from other loaders.
|
|
||||||
*
|
|
||||||
* @package twig
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class Twig_Loader_Chain implements Twig_LoaderInterface
|
|
||||||
{
|
|
||||||
protected $loaders;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param Twig_LoaderInterface[] $loaders An array of loader instances
|
|
||||||
*/
|
|
||||||
public function __construct(array $loaders = array())
|
|
||||||
{
|
|
||||||
$this->loaders = array();
|
|
||||||
foreach ($loaders as $loader) {
|
|
||||||
$this->addLoader($loader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a loader instance.
|
|
||||||
*
|
|
||||||
* @param Twig_LoaderInterface $loader A Loader instance
|
|
||||||
*/
|
|
||||||
public function addLoader(Twig_LoaderInterface $loader)
|
|
||||||
{
|
|
||||||
$this->loaders[] = $loader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the source code of a template, given its name.
|
|
||||||
*
|
|
||||||
* @param string $name The name of the template to load
|
|
||||||
*
|
|
||||||
* @return string The template source code
|
|
||||||
*/
|
|
||||||
public function getSource($name)
|
|
||||||
{
|
|
||||||
foreach ($this->loaders as $loader) {
|
|
||||||
try {
|
|
||||||
return $loader->getSource($name);
|
|
||||||
} catch (Twig_Error_Loader $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the cache key to use for the cache for a given template name.
|
|
||||||
*
|
|
||||||
* @param string $name The name of the template to load
|
|
||||||
*
|
|
||||||
* @return string The cache key
|
|
||||||
*/
|
|
||||||
public function getCacheKey($name)
|
|
||||||
{
|
|
||||||
foreach ($this->loaders as $loader) {
|
|
||||||
try {
|
|
||||||
return $loader->getCacheKey($name);
|
|
||||||
} catch (Twig_Error_Loader $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the template is still fresh.
|
|
||||||
*
|
|
||||||
* @param string $name The template name
|
|
||||||
* @param timestamp $time The last modification time of the cached template
|
|
||||||
*/
|
|
||||||
public function isFresh($name, $time)
|
|
||||||
{
|
|
||||||
foreach ($this->loaders as $loader) {
|
|
||||||
try {
|
|
||||||
return $loader->isFresh($name, $time);
|
|
||||||
} catch (Twig_Error_Loader $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2009 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads template from the filesystem.
|
|
||||||
*
|
|
||||||
* @package twig
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class Twig_Loader_Filesystem implements Twig_LoaderInterface
|
|
||||||
{
|
|
||||||
protected $paths;
|
|
||||||
protected $cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param string|array $paths A path or an array of paths where to look for templates
|
|
||||||
*/
|
|
||||||
public function __construct($paths)
|
|
||||||
{
|
|
||||||
$this->setPaths($paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the paths to the templates.
|
|
||||||
*
|
|
||||||
* @return array The array of paths where to look for templates
|
|
||||||
*/
|
|
||||||
public function getPaths()
|
|
||||||
{
|
|
||||||
return $this->paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the paths where templates are stored.
|
|
||||||
*
|
|
||||||
* @param string|array $paths A path or an array of paths where to look for templates
|
|
||||||
*/
|
|
||||||
public function setPaths($paths)
|
|
||||||
{
|
|
||||||
if (!is_array($paths)) {
|
|
||||||
$paths = array($paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->paths = array();
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$this->addPath($path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a path where templates are stored.
|
|
||||||
*
|
|
||||||
* @param string $path A path where to look for templates
|
|
||||||
*/
|
|
||||||
public function addPath($path)
|
|
||||||
{
|
|
||||||
// invalidate the cache
|
|
||||||
$this->cache = array();
|
|
||||||
|
|
||||||
if (!is_dir($path)) {
|
|
||||||
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->paths[] = $path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the source code of a template, given its name.
|
|
||||||
*
|
|
||||||
* @param string $name The name of the template to load
|
|
||||||
*
|
|
||||||
* @return string The template source code
|
|
||||||
*/
|
|
||||||
public function getSource($name)
|
|
||||||
{
|
|
||||||
return file_get_contents($this->findTemplate($name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the cache key to use for the cache for a given template name.
|
|
||||||
*
|
|
||||||
* @param string $name The name of the template to load
|
|
||||||
*
|
|
||||||
* @return string The cache key
|
|
||||||
*/
|
|
||||||
public function getCacheKey($name)
|
|
||||||
{
|
|
||||||
return $this->findTemplate($name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the template is still fresh.
|
|
||||||
*
|
|
||||||
* @param string $name The template name
|
|
||||||
* @param timestamp $time The last modification time of the cached template
|
|
||||||
*/
|
|
||||||
public function isFresh($name, $time)
|
|
||||||
{
|
|
||||||
return filemtime($this->findTemplate($name)) < $time;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function findTemplate($name)
|
|
||||||
{
|
|
||||||
// normalize name
|
|
||||||
$name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
|
|
||||||
|
|
||||||
if (isset($this->cache[$name])) {
|
|
||||||
return $this->cache[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->validateName($name);
|
|
||||||
|
|
||||||
foreach ($this->paths as $path) {
|
|
||||||
if (is_file($path.'/'.$name)) {
|
|
||||||
return $this->cache[$name] = $path.'/'.$name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function validateName($name)
|
|
||||||
{
|
|
||||||
if (false !== strpos($name, "\0")) {
|
|
||||||
throw new Twig_Error_Loader('A template name cannot contain NUL bytes.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$parts = explode('/', $name);
|
|
||||||
$level = 0;
|
|
||||||
foreach ($parts as $part) {
|
|
||||||
if ('..' === $part) {
|
|
||||||
--$level;
|
|
||||||
} elseif ('.' !== $part) {
|
|
||||||
++$level;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($level < 0) {
|
|
||||||
throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2009 Fabien Potencier
|
|
||||||
* (c) 2009 Armin Ronacher
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
class Twig_Node_Expression_Filter extends Twig_Node_Expression
|
|
||||||
{
|
|
||||||
public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null)
|
|
||||||
{
|
|
||||||
parent::__construct(array('node' => $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function compile(Twig_Compiler $compiler)
|
|
||||||
{
|
|
||||||
$name = $this->getNode('filter')->getAttribute('value');
|
|
||||||
|
|
||||||
if (false === $filter = $compiler->getEnvironment()->getFilter($name)) {
|
|
||||||
$message = sprintf('The filter "%s" does not exist', $name);
|
|
||||||
if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getFilters()))) {
|
|
||||||
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Syntax($message, $this->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->compileFilter($compiler, $filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function compileFilter(Twig_Compiler $compiler, Twig_FilterInterface $filter)
|
|
||||||
{
|
|
||||||
$compiler
|
|
||||||
->raw($filter->compile().'(')
|
|
||||||
->raw($filter->needsEnvironment() ? '$this->env, ' : '')
|
|
||||||
->raw($filter->needsContext() ? '$context, ' : '')
|
|
||||||
;
|
|
||||||
|
|
||||||
foreach ($filter->getArguments() as $argument) {
|
|
||||||
$compiler
|
|
||||||
->string($argument)
|
|
||||||
->raw(', ')
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
$compiler->subcompile($this->getNode('node'));
|
|
||||||
|
|
||||||
foreach ($this->getNode('arguments') as $node) {
|
|
||||||
$compiler
|
|
||||||
->raw(', ')
|
|
||||||
->subcompile($node)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
$compiler->raw(')');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2010 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
class Twig_Node_Expression_Function extends Twig_Node_Expression
|
|
||||||
{
|
|
||||||
public function __construct($name, Twig_NodeInterface $arguments, $lineno)
|
|
||||||
{
|
|
||||||
parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function compile(Twig_Compiler $compiler)
|
|
||||||
{
|
|
||||||
$name = $this->getAttribute('name');
|
|
||||||
|
|
||||||
if (false === $function = $compiler->getEnvironment()->getFunction($name)) {
|
|
||||||
$message = sprintf('The function "%s" does not exist', $name);
|
|
||||||
if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getFunctions()))) {
|
|
||||||
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Syntax($message, $this->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
$compiler->raw($function->compile().'(');
|
|
||||||
|
|
||||||
$first = true;
|
|
||||||
|
|
||||||
if ($function->needsEnvironment()) {
|
|
||||||
$compiler->raw('$this->env');
|
|
||||||
$first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($function->needsContext()) {
|
|
||||||
if (!$first) {
|
|
||||||
$compiler->raw(', ');
|
|
||||||
}
|
|
||||||
$compiler->raw('$context');
|
|
||||||
$first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($function->getArguments() as $argument) {
|
|
||||||
if (!$first) {
|
|
||||||
$compiler->raw(', ');
|
|
||||||
}
|
|
||||||
$compiler->string($argument);
|
|
||||||
$first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->getNode('arguments') as $node) {
|
|
||||||
if (!$first) {
|
|
||||||
$compiler->raw(', ');
|
|
||||||
}
|
|
||||||
$compiler->subcompile($node);
|
|
||||||
$first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$compiler->raw(')');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2010 Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
class Twig_Node_Expression_Test extends Twig_Node_Expression
|
|
||||||
{
|
|
||||||
public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
|
|
||||||
{
|
|
||||||
parent::__construct(array('node' => $node, 'arguments' => $arguments), array('name' => $name), $lineno);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function compile(Twig_Compiler $compiler)
|
|
||||||
{
|
|
||||||
$name = $this->getAttribute('name');
|
|
||||||
$testMap = $compiler->getEnvironment()->getTests();
|
|
||||||
if (!isset($testMap[$name])) {
|
|
||||||
$message = sprintf('The test "%s" does not exist', $name);
|
|
||||||
if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getTests()))) {
|
|
||||||
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Twig_Error_Syntax($message, $this->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $this->getAttribute('name');
|
|
||||||
$node = $this->getNode('node');
|
|
||||||
|
|
||||||
$compiler
|
|
||||||
->raw($testMap[$name]->compile().'(')
|
|
||||||
->subcompile($node)
|
|
||||||
;
|
|
||||||
|
|
||||||
if (null !== $this->getNode('arguments')) {
|
|
||||||
$compiler->raw(', ');
|
|
||||||
|
|
||||||
$max = count($this->getNode('arguments')) - 1;
|
|
||||||
foreach ($this->getNode('arguments') as $i => $arg) {
|
|
||||||
$compiler->subcompile($arg);
|
|
||||||
|
|
||||||
if ($i != $max) {
|
|
||||||
$compiler->raw(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$compiler->raw(')');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) 2009 Fabien Potencier
|
|
||||||
* (c) 2009 Armin Ronacher
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops over each item of a sequence.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* <ul>
|
|
||||||
* {% for user in users %}
|
|
||||||
* <li>{{ user.username|e }}</li>
|
|
||||||
* {% endfor %}
|
|
||||||
* </ul>
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
class Twig_TokenParser_For extends Twig_TokenParser
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Parses a token and returns a node.
|
|
||||||
*
|
|
||||||
* @param Twig_Token $token A Twig_Token instance
|
|
||||||
*
|
|
||||||
* @return Twig_NodeInterface A Twig_NodeInterface instance
|
|
||||||
*/
|
|
||||||
public function parse(Twig_Token $token)
|
|
||||||
{
|
|
||||||
$lineno = $token->getLine();
|
|
||||||
$targets = $this->parser->getExpressionParser()->parseAssignmentExpression();
|
|
||||||
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, 'in');
|
|
||||||
$seq = $this->parser->getExpressionParser()->parseExpression();
|
|
||||||
|
|
||||||
$ifexpr = null;
|
|
||||||
if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'if')) {
|
|
||||||
$this->parser->getStream()->next();
|
|
||||||
$ifexpr = $this->parser->getExpressionParser()->parseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
|
||||||
$body = $this->parser->subparse(array($this, 'decideForFork'));
|
|
||||||
if ($this->parser->getStream()->next()->getValue() == 'else') {
|
|
||||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
|
||||||
$else = $this->parser->subparse(array($this, 'decideForEnd'), true);
|
|
||||||
} else {
|
|
||||||
$else = null;
|
|
||||||
}
|
|
||||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
|
||||||
|
|
||||||
if (count($targets) > 1) {
|
|
||||||
$keyTarget = $targets->getNode(0);
|
|
||||||
$keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getLine());
|
|
||||||
$valueTarget = $targets->getNode(1);
|
|
||||||
$valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine());
|
|
||||||
} else {
|
|
||||||
$keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno);
|
|
||||||
$valueTarget = $targets->getNode(0);
|
|
||||||
$valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function decideForFork(Twig_Token $token)
|
|
||||||
{
|
|
||||||
return $token->test(array('else', 'endfor'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function decideForEnd(Twig_Token $token)
|
|
||||||
{
|
|
||||||
return $token->test('endfor');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the tag name associated with this token parser.
|
|
||||||
*
|
|
||||||
* @return string The tag name
|
|
||||||
*/
|
|
||||||
public function getTag()
|
|
||||||
{
|
|
||||||
return 'for';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<package packagerversion="1.8.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
|
|
||||||
http://pear.php.net/dtd/tasks-1.0.xsd
|
|
||||||
http://pear.php.net/dtd/package-2.0
|
|
||||||
http://pear.php.net/dtd/package-2.0.xsd">
|
|
||||||
<name>Twig</name>
|
|
||||||
<channel>pear.twig-project.org</channel>
|
|
||||||
<summary>Twig is a PHP template engine.</summary>
|
|
||||||
<description>
|
|
||||||
Twig is a template language for PHP, released under the new BSD license
|
|
||||||
(code and documentation).
|
|
||||||
|
|
||||||
Twig uses a syntax similar to the Django and Jinja template languages which
|
|
||||||
inspired the Twig runtime environment.
|
|
||||||
</description>
|
|
||||||
<lead>
|
|
||||||
<name>Fabien Potencier</name>
|
|
||||||
<user>fabpot</user>
|
|
||||||
<email>fabien.potencier@symfony-project.org</email>
|
|
||||||
<active>yes</active>
|
|
||||||
</lead>
|
|
||||||
<lead>
|
|
||||||
<name>Armin Ronacher</name>
|
|
||||||
<user>armin</user>
|
|
||||||
<email>armin.ronacher@active-4.com</email>
|
|
||||||
<active>no</active>
|
|
||||||
</lead>
|
|
||||||
<date>{{ date }}</date>
|
|
||||||
<time>{{ time }}</time>
|
|
||||||
<version>
|
|
||||||
<release>{{ version }}</release>
|
|
||||||
<api>{{ api_version }}</api>
|
|
||||||
</version>
|
|
||||||
<stability>
|
|
||||||
<release>{{ stability }}</release>
|
|
||||||
<api>{{ stability }}</api>
|
|
||||||
</stability>
|
|
||||||
<license uri="http://www.opensource.org/licenses/bsd-license.php">BSD Style</license>
|
|
||||||
<notes>-</notes>
|
|
||||||
<contents>
|
|
||||||
<dir name="/">
|
|
||||||
<file name="AUTHORS" role="doc" />
|
|
||||||
<file name="CHANGELOG" role="doc" />
|
|
||||||
<file name="LICENSE" role="doc" />
|
|
||||||
<file name="README.markdown" role="doc" />
|
|
||||||
<dir name="lib">
|
|
||||||
<dir name="Twig">
|
|
||||||
{{ files }}
|
|
||||||
</dir>
|
|
||||||
</dir>
|
|
||||||
</dir>
|
|
||||||
</contents>
|
|
||||||
<dependencies>
|
|
||||||
<required>
|
|
||||||
<php>
|
|
||||||
<min>5.2.4</min>
|
|
||||||
</php>
|
|
||||||
<pearinstaller>
|
|
||||||
<min>1.4.0</min>
|
|
||||||
</pearinstaller>
|
|
||||||
</required>
|
|
||||||
</dependencies>
|
|
||||||
<phprelease />
|
|
||||||
</package>
|
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Twig_Tests_AutoloaderTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
public function testAutoload()
|
|
||||||
{
|
|
||||||
$this->assertFalse(class_exists('FooBarFoo'), '->autoload() does not try to load classes that does not begin with Twig');
|
|
||||||
|
|
||||||
$autoloader = new Twig_Autoloader();
|
|
||||||
$this->assertNull($autoloader->autoload('Foo'), '->autoload() returns false if it is not able to load a class');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Twig_Tests_CompilerTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
public function testReprNumericValueWithLocale()
|
|
||||||
{
|
|
||||||
$compiler = new Twig_Compiler(new Twig_Environment());
|
|
||||||
|
|
||||||
$locale = setlocale(LC_NUMERIC, 0);
|
|
||||||
if (false === $locale) {
|
|
||||||
$this->markTestSkipped('Your platform does not support locales.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$required_locales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252');
|
|
||||||
if (false === setlocale(LC_ALL, $required_locales)) {
|
|
||||||
$this->markTestSkipped('Could not set any of required locales: ' . implode(", ", $required_locales));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertEquals('1.2', $compiler->repr(1.2)->getSource());
|
|
||||||
$this->assertContains('fr', strtolower(setlocale(LC_NUMERIC, 0)));
|
|
||||||
|
|
||||||
setlocale(LC_ALL, $locale);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
public function testTwigExceptionAddsFileAndLineWhenMissing()
|
|
||||||
{
|
|
||||||
$loader = new Twig_Loader_Array(array('index' => "\n\n{{ foo.bar }}"));
|
|
||||||
$twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
|
|
||||||
|
|
||||||
$template = $twig->loadTemplate('index');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$template->render(array());
|
|
||||||
|
|
||||||
$this->fail();
|
|
||||||
} catch (Twig_Error_Runtime $e) {
|
|
||||||
$this->assertEquals('Variable "foo" does not exist in "index" at line 3', $e->getMessage());
|
|
||||||
$this->assertEquals(3, $e->getTemplateLine());
|
|
||||||
$this->assertEquals('index', $e->getTemplateFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRenderWrapsExceptions()
|
|
||||||
{
|
|
||||||
$loader = new Twig_Loader_Array(array('index' => "\n\n\n{{ foo.bar }}"));
|
|
||||||
$twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
|
|
||||||
|
|
||||||
$template = $twig->loadTemplate('index');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$template->render(array('foo' => new Twig_Tests_ErrorTest_Foo()));
|
|
||||||
|
|
||||||
$this->fail();
|
|
||||||
} catch (Twig_Error_Runtime $e) {
|
|
||||||
$this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index" at line 4.', $e->getMessage());
|
|
||||||
$this->assertEquals(4, $e->getTemplateLine());
|
|
||||||
$this->assertEquals('index', $e->getTemplateFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritance()
|
|
||||||
{
|
|
||||||
$loader = new Twig_Loader_Array(array(
|
|
||||||
'index' => "{% extends 'base' %}
|
|
||||||
{% block content %}
|
|
||||||
{{ foo.bar }}
|
|
||||||
{% endblock %}",
|
|
||||||
'base' => '{% block content %}{% endblock %}'
|
|
||||||
));
|
|
||||||
$twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
|
|
||||||
|
|
||||||
$template = $twig->loadTemplate('index');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$template->render(array());
|
|
||||||
|
|
||||||
$this->fail();
|
|
||||||
} catch (Twig_Error_Runtime $e) {
|
|
||||||
$this->assertEquals('Variable "foo" does not exist in "index" at line 3', $e->getMessage());
|
|
||||||
$this->assertEquals(3, $e->getTemplateLine());
|
|
||||||
$this->assertEquals('index', $e->getTemplateFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Twig_Tests_ErrorTest_Foo
|
|
||||||
{
|
|
||||||
public function bar()
|
|
||||||
{
|
|
||||||
throw new Exception('Runtime error...');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @expectedException Twig_Error_Syntax
|
|
||||||
* @dataProvider getFailingTestsForAssignment
|
|
||||||
*/
|
|
||||||
public function testCanOnlyAssignToNames($template)
|
|
||||||
{
|
|
||||||
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
|
|
||||||
$parser = new Twig_Parser($env);
|
|
||||||
|
|
||||||
$parser->parse($env->tokenize($template, 'index'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFailingTestsForAssignment()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
array('{% set false = "foo" %}'),
|
|
||||||
array('{% set true = "foo" %}'),
|
|
||||||
array('{% set none = "foo" %}'),
|
|
||||||
array('{% set 3 = "foo" %}'),
|
|
||||||
array('{% set 1 + 2 = "foo" %}'),
|
|
||||||
array('{% set "bar" = "foo" %}'),
|
|
||||||
array('{% set %}{% endset %}')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider getTestsForArray
|
|
||||||
*/
|
|
||||||
public function testArrayExpression($template, $expected)
|
|
||||||
{
|
|
||||||
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
|
|
||||||
$stream = $env->tokenize($template, 'index');
|
|
||||||
$parser = new Twig_Parser($env);
|
|
||||||
|
|
||||||
$this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException Twig_Error_Syntax
|
|
||||||
* @dataProvider getFailingTestsForArray
|
|
||||||
*/
|
|
||||||
public function testArraySyntaxError($template)
|
|
||||||
{
|
|
||||||
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
|
|
||||||
$parser = new Twig_Parser($env);
|
|
||||||
|
|
||||||
$parser->parse($env->tokenize($template, 'index'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFailingTestsForArray()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
array('{{ [1, "a": "b"] }}'),
|
|
||||||
array('{{ {"a": "b", 2} }}'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTestsForArray()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
// simple array
|
|
||||||
array('{{ [1, 2] }}', new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant(0, 1),
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
new Twig_Node_Expression_Constant(2, 1),
|
|
||||||
), 1),
|
|
||||||
),
|
|
||||||
|
|
||||||
// array with trailing ,
|
|
||||||
array('{{ [1, 2, ] }}', new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant(0, 1),
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
new Twig_Node_Expression_Constant(2, 1),
|
|
||||||
), 1),
|
|
||||||
),
|
|
||||||
|
|
||||||
// simple hash
|
|
||||||
array('{{ {"a": "b", "b": "c"} }}', new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant('a', 1),
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
new Twig_Node_Expression_Constant('c', 1),
|
|
||||||
), 1),
|
|
||||||
),
|
|
||||||
|
|
||||||
// hash with trailing ,
|
|
||||||
array('{{ {"a": "b", "b": "c", } }}', new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant('a', 1),
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
new Twig_Node_Expression_Constant('c', 1),
|
|
||||||
), 1),
|
|
||||||
),
|
|
||||||
|
|
||||||
// hash in an array
|
|
||||||
array('{{ [1, {"a": "b", "b": "c"}] }}', new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant(0, 1),
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant('a', 1),
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
new Twig_Node_Expression_Constant('c', 1),
|
|
||||||
), 1),
|
|
||||||
), 1),
|
|
||||||
),
|
|
||||||
|
|
||||||
// array in a hash
|
|
||||||
array('{{ {"a": [1, 2], "b": "c"} }}', new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant('a', 1),
|
|
||||||
new Twig_Node_Expression_Array(array(
|
|
||||||
new Twig_Node_Expression_Constant(0, 1),
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
|
|
||||||
new Twig_Node_Expression_Constant(1, 1),
|
|
||||||
new Twig_Node_Expression_Constant(2, 1),
|
|
||||||
), 1),
|
|
||||||
new Twig_Node_Expression_Constant('b', 1),
|
|
||||||
new Twig_Node_Expression_Constant('c', 1),
|
|
||||||
), 1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException Twig_Error_Syntax
|
|
||||||
*/
|
|
||||||
public function testStringExpressionDoesNotConcatenateTwoConsecutiveStrings()
|
|
||||||
{
|
|
||||||
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0));
|
|
||||||
$stream = $env->tokenize('{{ "a" "b" }}', 'index');
|
|
||||||
$parser = new Twig_Parser($env);
|
|
||||||
|
|
||||||
$parser->parse($stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider getTestsForString
|
|
||||||
*/
|
|
||||||
public function testStringExpression($template, $expected)
|
|
||||||
{
|
|
||||||
$env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0));
|
|
||||||
$stream = $env->tokenize($template, 'index');
|
|
||||||
$parser = new Twig_Parser($env);
|
|
||||||
|
|
||||||
$this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTestsForString()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
array(
|
|
||||||
'{{ "foo" }}', new Twig_Node_Expression_Constant('foo', 1),
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'{{ "foo #{bar}" }}', new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Constant('foo ', 1),
|
|
||||||
new Twig_Node_Expression_Name('bar', 1),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'{{ "foo #{bar} baz" }}', new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Constant('foo ', 1),
|
|
||||||
new Twig_Node_Expression_Name('bar', 1),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
new Twig_Node_Expression_Constant(' baz', 1),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
array(
|
|
||||||
'{{ "foo #{"foo #{bar} baz"} baz" }}', new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Constant('foo ', 1),
|
|
||||||
new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Binary_Concat(
|
|
||||||
new Twig_Node_Expression_Constant('foo ', 1),
|
|
||||||
new Twig_Node_Expression_Name('bar', 1),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
new Twig_Node_Expression_Constant(' baz', 1),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
new Twig_Node_Expression_Constant(' baz', 1),
|
|
||||||
1
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @dataProvider getRandomFunctionTestData
|
|
||||||
*/
|
|
||||||
public function testRandomFunction($value, $expectedInArray)
|
|
||||||
{
|
|
||||||
for ($i = 0; $i < 100; $i++) {
|
|
||||||
$this->assertTrue(in_array(twig_random(new Twig_Environment(), $value), $expectedInArray, true)); // assertContains() would not consider the type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRandomFunctionTestData()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
array( // array
|
|
||||||
array('apple', 'orange', 'citrus'),
|
|
||||||
array('apple', 'orange', 'citrus'),
|
|
||||||
),
|
|
||||||
array( // Traversable
|
|
||||||
new ArrayObject(array('apple', 'orange', 'citrus')),
|
|
||||||
array('apple', 'orange', 'citrus'),
|
|
||||||
),
|
|
||||||
array( // unicode string
|
|
||||||
'Ä€é',
|
|
||||||
array('Ä', '€', 'é'),
|
|
||||||
),
|
|
||||||
array( // numeric but string
|
|
||||||
'123',
|
|
||||||
array('1', '2', '3'),
|
|
||||||
),
|
|
||||||
array( // integer
|
|
||||||
5,
|
|
||||||
range(0, 5, 1),
|
|
||||||
),
|
|
||||||
array( // float
|
|
||||||
5.9,
|
|
||||||
range(0, 5, 1),
|
|
||||||
),
|
|
||||||
array( // negative
|
|
||||||
-2,
|
|
||||||
array(0, -1, -2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRandomFunctionWithoutParameter()
|
|
||||||
{
|
|
||||||
$max = mt_getrandmax();
|
|
||||||
|
|
||||||
for ($i = 0; $i < 100; $i++) {
|
|
||||||
$val = twig_random(new Twig_Environment());
|
|
||||||
$this->assertTrue(is_int($val) && $val >= 0 && $val <= $max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRandomFunctionReturnsAsIs()
|
|
||||||
{
|
|
||||||
$this->assertSame('', twig_random(new Twig_Environment(), ''));
|
|
||||||
|
|
||||||
$instance = new stdClass();
|
|
||||||
$this->assertSame($instance, twig_random(new Twig_Environment(), $instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException Twig_Error_Runtime
|
|
||||||
*/
|
|
||||||
public function testRandomFunctionOfEmptyArrayThrowsException()
|
|
||||||
{
|
|
||||||
twig_random(new Twig_Environment(), array());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRandomFunctionOnNonUTF8String()
|
|
||||||
{
|
|
||||||
if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) {
|
|
||||||
$this->markTestSkipped('needs iconv or mbstring');
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = new Twig_Environment();
|
|
||||||
$twig->setCharset('ISO-8859-1');
|
|
||||||
|
|
||||||
$text = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8');
|
|
||||||
for ($i = 0; $i < 30; $i++) {
|
|
||||||
$rand = twig_random($twig, $text);
|
|
||||||
$this->assertTrue(in_array(twig_convert_encoding($rand, 'UTF-8', 'ISO-8859-1'), array('Ä', 'é'), true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testReverseFilterOnNonUTF8String()
|
|
||||||
{
|
|
||||||
if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) {
|
|
||||||
$this->markTestSkipped('needs iconv or mbstring');
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = new Twig_Environment();
|
|
||||||
$twig->setCharset('ISO-8859-1');
|
|
||||||
|
|
||||||
$input = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8');
|
|
||||||
$output = twig_convert_encoding(twig_reverse_filter($twig, $input), 'UTF-8', 'ISO-8859-1');
|
|
||||||
|
|
||||||
$this->assertEquals($output, 'éÄ');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of Twig.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
static protected $params, $templates;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
self::$params = array(
|
|
||||||
'name' => 'Fabien',
|
|
||||||
'obj' => new FooObject(),
|
|
||||||
'arr' => array('obj' => new FooObject()),
|
|
||||||
);
|
|
||||||
|
|
||||||
self::$templates = array(
|
|
||||||
'1_basic1' => '{{ obj.foo }}',
|
|
||||||
'1_basic2' => '{{ name|upper }}',
|
|
||||||
'1_basic3' => '{% if name %}foo{% endif %}',
|
|
||||||
'1_basic4' => '{{ obj.bar }}',
|
|
||||||
'1_basic5' => '{{ obj }}',
|
|
||||||
'1_basic6' => '{{ arr.obj }}',
|
|
||||||
'1_basic7' => '{{ cycle(["foo","bar"], 1) }}',
|
|
||||||
'1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',
|
|
||||||
'1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
|
|
||||||
'1_layout' => '{% block content %}{% endblock %}',
|
|
||||||
'1_child' => '{% extends "1_layout" %}{% block content %}{{ "a"|json_encode }}{% endblock %}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException Twig_Sandbox_SecurityError
|
|
||||||
* @expectedExceptionMessage Filter "json_encode" is not allowed in "1_child".
|
|
||||||
*/
|
|
||||||
public function testSandboxWithInheritance()
|
|
||||||
{
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array('block'));
|
|
||||||
$twig->loadTemplate('1_child')->render(array());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSandboxGloballySet()
|
|
||||||
{
|
|
||||||
$twig = $this->getEnvironment(false, array(), self::$templates);
|
|
||||||
$this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally');
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic1')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic2')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic3')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic4')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic5')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic6')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('1_basic7')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => 'foo'));
|
|
||||||
FooObject::reset();
|
|
||||||
$this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods');
|
|
||||||
$this->assertEquals(1, FooObject::$called['foo'], 'Sandbox only calls method once');
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => '__toString'));
|
|
||||||
FooObject::reset();
|
|
||||||
$this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods');
|
|
||||||
$this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper'));
|
|
||||||
$this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters');
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array('if'));
|
|
||||||
$this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags');
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('FooObject' => 'bar'));
|
|
||||||
$this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties');
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle'));
|
|
||||||
$this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions');
|
|
||||||
|
|
||||||
foreach (array('getfoobar', 'getFoobar', 'getFooBar') as $name) {
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => $name));
|
|
||||||
FooObject::reset();
|
|
||||||
$this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic8')->render(self::$params), 'Sandbox allow methods in a case-insensitive way');
|
|
||||||
$this->assertEquals(2, FooObject::$called['getFooBar'], 'Sandbox only calls method once');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSandboxLocallySetForAnInclude()
|
|
||||||
{
|
|
||||||
self::$templates = array(
|
|
||||||
'2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
|
|
||||||
'2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
|
|
||||||
);
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(false, array(), self::$templates);
|
|
||||||
$this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
|
|
||||||
|
|
||||||
self::$templates = array(
|
|
||||||
'3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
|
|
||||||
'3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
|
|
||||||
);
|
|
||||||
|
|
||||||
$twig = $this->getEnvironment(true, array(), self::$templates);
|
|
||||||
try {
|
|
||||||
$twig->loadTemplate('3_basic')->render(self::$params);
|
|
||||||
$this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');
|
|
||||||
} catch (Twig_Sandbox_SecurityError $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testMacrosInASandbox()
|
|
||||||
{
|
|
||||||
$twig = $this->getEnvironment(true, array('autoescape' => true), array('index' => <<<EOF
|
|
||||||
{% macro test(text) %}<p>{{ text }}</p>{% endmacro %}
|
|
||||||
{{ _self.test('username') }}
|
|
||||||
EOF
|
|
||||||
), array('macro'), array('escape'));
|
|
||||||
|
|
||||||
$this->assertEquals('<p>username</p>', $twig->loadTemplate('index')->render(array()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array())
|
|
||||||
{
|
|
||||||
$loader = new Twig_Loader_Array($templates);
|
|
||||||
$twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options));
|
|
||||||
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
|
|
||||||
$twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed));
|
|
||||||
|
|
||||||
return $twig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FooObject
|
|
||||||
{
|
|
||||||
static public $called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
|
|
||||||
|
|
||||||
public $bar = 'bar';
|
|
||||||
|
|
||||||
static public function reset()
|
|
||||||
{
|
|
||||||
self::$called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
++self::$called['__toString'];
|
|
||||||
|
|
||||||
return 'foo';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function foo()
|
|
||||||
{
|
|
||||||
++self::$called['foo'];
|
|
||||||
|
|
||||||
return 'foo';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFooBar()
|
|
||||||
{
|
|
||||||
++self::$called['getFooBar'];
|
|
||||||
|
|
||||||
return 'foobar';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Twig_Tests_FileCachingTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
protected $fileName;
|
|
||||||
protected $env;
|
|
||||||
protected $tmpDir;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->tmpDir = sys_get_temp_dir().'/TwigTests';
|
|
||||||
if (!file_exists($this->tmpDir)) {
|
|
||||||
@mkdir($this->tmpDir, 0777, true);;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_writable($this->tmpDir)) {
|
|
||||||
$this->markTestSkipped(sprintf('Unable to run the tests as "%s" is not writable.', $this->tmpDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->env = new Twig_Environment(new Twig_Loader_String(), array('cache' => $this->tmpDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tearDown()
|
|
||||||
{
|
|
||||||
if ($this->fileName) {
|
|
||||||
unlink($this->fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->removeDir($this->tmpDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWritingCacheFiles()
|
|
||||||
{
|
|
||||||
$name = 'This is just text.';
|
|
||||||
$template = $this->env->loadTemplate($name);
|
|
||||||
$cacheFileName = $this->env->getCacheFilename($name);
|
|
||||||
|
|
||||||
$this->assertTrue(file_exists($cacheFileName), 'Cache file does not exist.');
|
|
||||||
$this->fileName = $cacheFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testClearingCacheFiles()
|
|
||||||
{
|
|
||||||
$name = 'I will be deleted.';
|
|
||||||
$template = $this->env->loadTemplate($name);
|
|
||||||
$cacheFileName = $this->env->getCacheFilename($name);
|
|
||||||
|
|
||||||
$this->assertTrue(file_exists($cacheFileName), 'Cache file does not exist.');
|
|
||||||
$this->env->clearCacheFiles();
|
|
||||||
$this->assertFalse(file_exists($cacheFileName), 'Cache file was not cleared.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function removeDir($target)
|
|
||||||
{
|
|
||||||
$fp = opendir($target);
|
|
||||||
while (false !== $file = readdir($fp)) {
|
|
||||||
if (in_array($file, array('.', '..'))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_dir($target.'/'.$file)) {
|
|
||||||
self::removeDir($target.'/'.$file);
|
|
||||||
} else {
|
|
||||||
unlink($target.'/'.$file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir($fp);
|
|
||||||
rmdir($target);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
--TEST--
|
|
||||||
Exception for an unclosed tag
|
|
||||||
--TEMPLATE--
|
|
||||||
{% block foo %}
|
|
||||||
{% if foo %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% for i in fo %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
--EXCEPTION--
|
|
||||||
Twig_Error_Syntax: Unexpected tag name "endblock" (expecting closing tag for the "if" tag defined near line 4) in "index.twig" at line 16
|
|
@ -1,61 +0,0 @@
|
|||||||
--TEST--
|
|
||||||
Twig supports array notation
|
|
||||||
--TEMPLATE--
|
|
||||||
{# empty array #}
|
|
||||||
{{ []|join(',') }}
|
|
||||||
|
|
||||||
{{ [1, 2]|join(',') }}
|
|
||||||
{{ ['foo', "bar"]|join(',') }}
|
|
||||||
{{ {0: 1, 'foo': 'bar'}|join(',') }}
|
|
||||||
{{ {0: 1, 'foo': 'bar'}|keys|join(',') }}
|
|
||||||
|
|
||||||
{{ {0: 1, foo: 'bar'}|join(',') }}
|
|
||||||
{{ {0: 1, foo: 'bar'}|keys|join(',') }}
|
|
||||||
|
|
||||||
{# nested arrays #}
|
|
||||||
{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %}
|
|
||||||
{{ a[2]|join(',') }}
|
|
||||||
{{ a[3]["foo"]|join(',') }}
|
|
||||||
|
|
||||||
{# works even if [] is used inside the array #}
|
|
||||||
{{ [foo[bar]]|join(',') }}
|
|
||||||
|
|
||||||
{# elements can be any expression #}
|
|
||||||
{{ ['foo'|upper, bar|upper, bar == foo]|join(',') }}
|
|
||||||
|
|
||||||
{# arrays can have a trailing , like in PHP #}
|
|
||||||
{{
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
]|join(',')
|
|
||||||
}}
|
|
||||||
|
|
||||||
{# keys can be any expression #}
|
|
||||||
{% set a = 1 %}
|
|
||||||
{% set b = "foo" %}
|
|
||||||
{% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd' } %}
|
|
||||||
{{ ary|keys|join(',') }}
|
|
||||||
{{ ary|join(',') }}
|
|
||||||
--DATA--
|
|
||||||
return array('bar' => 'bar', 'foo' => array('bar' => 'bar'))
|
|
||||||
--EXPECT--
|
|
||||||
1,2
|
|
||||||
foo,bar
|
|
||||||
1,bar
|
|
||||||
0,foo
|
|
||||||
|
|
||||||
1,bar
|
|
||||||
0,foo
|
|
||||||
|
|
||||||
1,2
|
|
||||||
bar
|
|
||||||
|
|
||||||
bar
|
|
||||||
|
|
||||||
FOO,BAR,
|
|
||||||
|
|
||||||
1,2
|
|
||||||
|
|
||||||
1,foo,c,1foo
|
|
||||||
a,b,c,d
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user