* 1.12.0 (2012-XX-XX)
+ * added support for named arguments for filters, tests, and functions
* moved filters/functions/tests syntax errors to the parser
* added support for extended ternary operator syntaxes
them must be installed. In case both are installed, `mbstring`_ is used by
default (Twig before 1.8.1 uses `iconv`_ by default).
+Arguments
+---------
+
+ * ``from``: The input charset
+ * ``to``: The output charset
+
.. _`iconv`: http://php.net/iconv
.. _`mbstring`: http://php.net/mbstring
$twig = new Twig_Environment($loader);
$twig->getExtension('core')->setTimezone('Europe/Paris');
+Arguments
+---------
+
+ * ``format``: The date format
+ * ``timezone``: The date timezone
+
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime
.. _`DateInterval`: http://www.php.net/DateInterval
by the `strtotime`_ function) or `DateTime`_ instances. You can easily combine
it with the :doc:`date<date>` filter for formatting.
+Arguments
+---------
+
+ * ``modifier``: The modifier
+
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime
Read the documentation for the :doc:`defined<../tests/defined>` and
:doc:`empty<../tests/empty>` tests to learn more about their semantics.
+
+Arguments
+---------
+
+ * ``default``: The default value
{{ var|escape(strategy)|raw }} {# won't be double-escaped #}
{% endautoescape %}
+Arguments
+---------
+
+ * ``strategy``: The escaping strategy
+ * ``charset``: The string charset
+
.. _`htmlspecialchars`: http://php.net/htmlspecialchars
{{ [1, 2, 3]|join('|') }}
{# returns 1|2|3 #}
+
+Arguments
+---------
+
+ * ``glue``: The separator
Internally, Twig uses the PHP `json_encode`_ function.
+Arguments
+---------
+
+ * ``options``: The options
+
.. _`json_encode`: http://php.net/json_encode
The defaults set for ``number_format`` can be over-ridden upon each call using the
additional parameters.
+Arguments
+---------
+
+ * ``decimal``: The number of decimal points to display
+ * ``decimal_point``: The character(s) to use for the decimal point
+ * ``decimal_sep``: The character(s) to use for the thousands separator
+
.. _`number_format`: http://php.net/number_format
{# returns I like foo and bar
if the foo parameter equals to the foo string. #}
+Arguments
+---------
+
+ * ``replace_pairs``: The placeholder values
+
.. seealso:: :doc:`format<format>`
It also works with objects implementing the `Traversable`_ interface.
+Arguments
+---------
+
+ * ``start``: The start of the slice
+ * ``length``: The size of the slice
+ * ``preserve_keys``: Whether to preserve key or not (when the input is an array)
+
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
.. _`array_slice`: http://php.net/array_slice
.. _`substr`: http://php.net/substr
Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is
empty) functions for string splitting.
+Arguments
+---------
+
+ * ``delimiter``: The delimiter
+ * ``limit``: The limit argument
+
.. _`explode`: http://php.net/explode
.. _`str_split`: http://php.net/str_split
Internally, Twig uses the PHP `trim`_ function.
+Arguments
+---------
+
+ * ``character_mask``: The characters to strip
+
.. _`trim`: http://php.net/trim
{% for i in 0..10 %}
{{ cycle(fruits, i) }}
{% endfor %}
+
+Arguments
+---------
+
+ * ``position``: The cycle position
$twig = new Twig_Environment($loader);
$twig->getExtension('core')->setTimezone('Europe/Paris');
+Arguments
+---------
+
+ * ``date``: The date
+ * ``timezone``: The timezone
+
.. _`date`: http://www.php.net/date
Internally, Twig uses the PHP `var_dump`_ function.
+Arguments
+---------
+
+ * ``context``: The context to dump
+
.. _`XDebug`: http://xdebug.org/docs/display
.. _`var_dump`: http://php.net/var_dump
{{ random() }} {# example output: 15386094 (works as native PHP `mt_rand`_ function) #}
{{ random(5) }} {# example output: 3 #}
+Arguments
+---------
+
+ * ``values``: The values
+
.. _`mt_rand`: http://php.net/mt_rand
The ``range`` function works as the native PHP `range`_ function.
+Arguments
+---------
+
+ * ``low``: The first value of the sequence.
+ * ``high``: The highest possible value of the sequence.
+ * ``step``: The increment between elements of the sequence.
+
.. _`range`: http://php.net/range
Even if you will probably always use the ``template_from_string`` function
with the ``include`` tag, you can use it with any tag or function that
takes a template as an argument (like the ``embed`` or ``extends`` tags).
+
+Arguments
+---------
+
+ * ``template``: The template
Go to the :doc:`functions<functions/index>` page to learn more about the
built-in functions.
+Named Arguments
+---------------
+
+.. versionadded:: 1.12
+ Support for named arguments was added in Twig 1.12.
+
+Arguments for filters and functions can also be passed as *named arguments*:
+
+.. code-block:: jinja
+
+ {% for i in range(low=1, high=10, step=2) %}
+ {{ i }},
+ {% endfor %}
+
+Using named arguments makes your templates more explicit about the meaning of
+the values you pass as arguments:
+
+.. code-block:: jinja
+
+ {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
+
+ {# versus #}
+
+ {{ data|convert_encoding(from='iso-2022-jp', to='UTF-8') }}
+
+Named arguments also allow you to skip some arguments for which you don't want
+to change the default value::
+
+.. code-block:: jinja
+
+ {# the first argument is the date format, which defaults to the global date format if null is passed #}
+ {{ "now"|date(null, "Europe/Paris") }}
+
+ {# or skip the format value by using a named argument for the timezone #}
+ {{ "now"|date(timezone="Europe/Paris") }}
+
+You can also use both positional and named arguments in one call, which is not
+recommended as it can be confusing:
+
+.. code-block:: jinja
+
+ {# both work #}
+ {{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}
+ {{ "now"|date(timezone="Europe/Paris", 'd/m/Y H:i') }}
+
+.. tip::
+
+ Each function and filter documentation page has a section where the names
+ of all arguments are listed when supported.
+
Control Structure
-----------------
public function getFunctionNode($name, $line)
{
- $args = $this->parseArguments();
switch ($name) {
case 'parent':
+ $args = $this->parseArguments();
if (!count($this->parser->getBlockStack())) {
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
case 'block':
- return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line);
+ return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
case 'attribute':
+ $args = $this->parseArguments();
if (count($args) < 2) {
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
default:
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new Twig_Node_Expression_Array(array(), $line);
- foreach ($args as $n) {
+ foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
return $node;
}
+ $args = $this->parseArguments(true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = new Twig_Node();
} else {
- $arguments = $this->parseArguments();
+ $arguments = $this->parseArguments(true);
}
$class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
return $node;
}
- public function parseArguments()
+ /**
+ * Parses arguments.
+ *
+ * @param Boolean $namedArguments Whether to allow named arguments or not
+ * @param Boolean $definition Whether we are parsing arguments for a function definition
+ */
+ public function parseArguments($namedArguments = false, $definition = false)
{
$args = array();
$stream = $this->parser->getStream();
- $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis');
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
if (!empty($args)) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
}
- $args[] = $this->parseExpression();
+
+ $value = $this->parseExpression();
+
+ $name = null;
+ if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
+ $token = $stream->next();
+ if (!$value instanceof Twig_Node_Expression_Name) {
+ throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine());
+ }
+ $name = $value->getAttribute('name');
+ $value = $definition ? $this->parsePrimaryExpression() : $this->parseExpression();
+ }
+
+ if (null === $name) {
+ $args[] = $value;
+ } else {
+ $args[$name] = $value;
+ }
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$arguments = null;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
- $arguments = $parser->getExpressionParser()->parseArguments();
+ $arguments = $parser->getExpressionParser()->parseArguments(true);
}
$class = $this->getTestNodeClass($parser, $name, $node->getLine());
/**
* Cycles over a value.
*
- * @param ArrayAccess|array $values An array or an ArrayAccess instance
- * @param integer $i The cycle value
+ * @param ArrayAccess|array $values An array or an ArrayAccess instance
+ * @param integer $position The cycle position
*
* @return string The next value in the cycle
*/
-function twig_cycle($values, $i)
+function twig_cycle($values, $position)
{
if (!is_array($values) && !$values instanceof ArrayAccess) {
return $values;
}
- return $values[$i % count($values)];
+ return $values[$position % count($values)];
}
/**
* Returns a new date object modified
*
* <pre>
- * {{ post.published_at|modify("-1day")|date("m/d/Y") }}
+ * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
-abstract class Twig_Filter implements Twig_FilterInterface
+abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
{
protected $options;
protected $arguments = array();
'needs_context' => false,
'pre_escape' => null,
'preserves_safety' => null,
+ 'callable' => null,
), $options);
}
{
return $this->options['pre_escape'];
}
+
+ public function getCallable()
+ {
+ return $this->options['callable'];
+ }
}
public function __construct($function, array $options = array())
{
+ $options['callable'] = $function;
+
parent::__construct($options);
$this->function = $function;
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
+ $options['callable'] = array($extension, $method);
+
parent::__construct($options);
$this->extension = $extension;
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2012 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a callable template filter.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface Twig_FilterCallableInterface
+{
+ public function getCallable();
+}
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
-abstract class Twig_Function implements Twig_FunctionInterface
+abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
{
protected $options;
protected $arguments = array();
$this->options = array_merge(array(
'needs_environment' => false,
'needs_context' => false,
+ 'callable' => null,
), $options);
}
return array();
}
+
+ public function getCallable()
+ {
+ return $this->options['callable'];
+ }
}
public function __construct($function, array $options = array())
{
+ $options['callable'] = $function;
+
parent::__construct($options);
$this->function = $function;
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
+ $options['callable'] = array($extension, $method);
+
parent::__construct($options);
$this->extension = $extension;
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2012 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a callable template function.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface Twig_FunctionCallableInterface
+{
+ public function getCallable();
+}
*/
abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
{
- public function compileArguments(Twig_Compiler $compiler)
+ protected function compileArguments(Twig_Compiler $compiler)
{
$compiler->raw('(');
}
if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) {
- foreach ($this->getNode('arguments') as $node) {
+ $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
+
+ $arguments = $this->getArguments($callable, $this->getNode('arguments'));
+
+ foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->raw(')');
}
+
+ protected function getArguments($callable, $arguments)
+ {
+ $parameters = array();
+ $named = false;
+ foreach ($arguments as $name => $node) {
+ if (!is_int($name)) {
+ $named = true;
+ $name = $this->normalizeName($name);
+ }
+ $parameters[$name] = $node;
+ }
+
+ if (!$named) {
+ return $parameters;
+ }
+
+ if (!$callable) {
+ throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
+ }
+
+ // manage named arguments
+ if (is_array($callable)) {
+ $r = new \ReflectionMethod($callable[0], $callable[1]);
+ } elseif (is_object($callable) && !$callable instanceof \Closure) {
+ $r = new \ReflectionObject($callable);
+ $r = $r->getMethod('__invoke');
+ } else {
+ $r = new \ReflectionFunction($callable);
+ }
+
+ $definition = $r->getParameters();
+ if ($this->hasNode('node')) {
+ array_shift($definition);
+ }
+ if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
+ array_shift($definition);
+ }
+ if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
+ array_shift($definition);
+ }
+ if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
+ foreach ($this->getAttribute('arguments') as $argument) {
+ array_shift($definition);
+ }
+ }
+
+ $arguments = array();
+ $pos = 0;
+ foreach ($definition as $param) {
+ $name = $this->normalizeName($param->name);
+
+ if (array_key_exists($name, $parameters)) {
+ $arguments[] = $parameters[$name];
+ unset($parameters[$name]);
+ } elseif (array_key_exists($pos, $parameters)) {
+ $arguments[] = $parameters[$pos];
+ unset($parameters[$pos]);
+ ++$pos;
+ } elseif ($param->isDefaultValueAvailable()) {
+ $arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
+ } elseif ($param->isOptional()) {
+ break;
+ } else {
+ throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
+ }
+ }
+
+ foreach (array_keys($parameters) as $name) {
+ throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
+ }
+
+ return $arguments;
+ }
+
+ protected function normalizeName($name)
+ {
+ return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name));
+ }
}
public function compile(Twig_Compiler $compiler)
{
- $filter = $compiler->getEnvironment()->getFilter($this->getNode('filter')->getAttribute('value'));
+ $name = $this->getNode('filter')->getAttribute('value');
+ $filter = $compiler->getEnvironment()->getFilter($name);
$compiler->raw($filter->compile());
+ $this->setAttribute('name', $name);
+ $this->setAttribute('type', 'filter');
$this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments());
+ if ($filter instanceof Twig_FilterCallableInterface) {
+ $this->setAttribute('callable', $filter->getCallable());
+ }
$this->compileArguments($compiler);
}
public function compile(Twig_Compiler $compiler)
{
- $function = $compiler->getEnvironment()->getFunction($this->getAttribute('name'));
+ $name = $this->getAttribute('name');
+ $function = $compiler->getEnvironment()->getFunction($name);
$compiler->raw($function->compile());
+ $this->setAttribute('name', $name);
+ $this->setAttribute('type', 'function');
$this->setAttribute('needs_environment', $function->needsEnvironment());
$this->setAttribute('needs_context', $function->needsContext());
$this->setAttribute('arguments', $function->getArguments());
+ if ($function instanceof Twig_FunctionCallableInterface) {
+ $this->setAttribute('callable', $function->getCallable());
+ }
$this->compileArguments($compiler);
}
{
$name = $this->getAttribute('name');
$testMap = $compiler->getEnvironment()->getTests();
+ $test = $testMap[$name];
- $compiler->raw($testMap[$name]->compile());
+ $this->setAttribute('name', $name);
+ $this->setAttribute('type', 'test');
+ if ($test instanceof Twig_TestCallableInterface) {
+ $this->setAttribute('callable', $test->getCallable());
+ }
+
+ $compiler->raw($test->compile());
$this->compileArguments($compiler);
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2012 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a template test.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface
+{
+ protected $options;
+ protected $arguments = array();
+
+ public function __construct(array $options = array())
+ {
+ $this->options = array_merge(array(
+ 'callable' => null,
+ ), $options);
+ }
+
+ public function getCallable()
+ {
+ return $this->options['callable'];
+ }
+}
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
-class Twig_Test_Function implements Twig_TestInterface
+class Twig_Test_Function extends Twig_Test
{
protected $function;
- public function __construct($function)
+ public function __construct($function, array $options = array())
{
+ $options['callable'] = $function;
+
+ parent::__construct($options);
+
$this->function = $function;
}
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
-class Twig_Test_Method implements Twig_TestInterface
+class Twig_Test_Method extends Twig_Test
{
protected $extension;
protected $method;
- public function __construct(Twig_ExtensionInterface $extension, $method)
+ public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{
+ $options['callable'] = array($extension, $method);
+
+ parent::__construct($options);
+
$this->extension = $extension;
$this->method = $method;
}
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
-class Twig_Test_Node implements Twig_TestInterface
+class Twig_Test_Node extends Twig_Test
{
protected $class;
- public function __construct($class)
+ public function __construct($class, array $options = array())
{
+ parent::__construct($options);
+
$this->class = $class;
}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2012 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a callable template test.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface Twig_TestCallableInterface
+{
+ public function getCallable();
+}
}
/**
+ * @expectedException Twig_Error_Syntax
+ */
+ public function testAttributeCallDoesNotSupportNamedArguments()
+ {
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
+ $parser = new Twig_Parser($env);
+
+ $parser->parse($env->tokenize('{{ foo.bar(name="Foo") }}', 'index'));
+ }
+
+ /**
+ * @expectedException Twig_Error_Syntax
+ */
+ public function testMacroCallDoesNotSupportNamedArguments()
+ {
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
+ $parser = new Twig_Parser($env);
+
+ $parser->parse($env->tokenize('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index'));
+ }
+
+ /**
* @expectedException Twig_Error_Syntax
* @expectedExceptionMessage The function "cycl" does not exist. Did you mean "cycle" in "index" at line 1
*/
--- /dev/null
+--TEST--
+"date" filter
+--TEMPLATE--
+{{ date|date(format='d/m/Y H:i:s P', timezone='America/Chicago') }}
+{{ date|date(timezone='America/Chicago', format='d/m/Y H:i:s P') }}
+{{ date|date(timezone='America/Chicago', 'd/m/Y H:i:s P') }}
+{{ date|date('d/m/Y H:i:s P', timezone='America/Chicago') }}
+--DATA--
+date_default_timezone_set('UTC');
+return array('date' => mktime(13, 45, 0, 10, 4, 2010))
+--EXPECT--
+04/10/2010 08:45:00 -05:00
+04/10/2010 08:45:00 -05:00
+04/10/2010 08:45:00 -05:00
+04/10/2010 08:45:00 -05:00
{{ [1, 2, 3, 4]|reverse|join('') }}
{{ '1234évènement'|reverse }}
{{ arr|reverse|join('') }}
+{{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }}
+{{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }}
+{{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }}
--DATA--
return array('arr' => new ArrayObject(array(1, 2, 3, 4)))
--EXPECT--
4321
tnemenèvé4321
4321
+a,c
+a,c
+a,c
--- /dev/null
+--TEST--
+"date" function
+--TEMPLATE--
+{{ date(date, "America/New_York")|date('d/m/Y H:i:s P', false) }}
+{{ date(timezone="America/New_York", date=date)|date('d/m/Y H:i:s P', false) }}
+--DATA--
+date_default_timezone_set('UTC');
+return array('date' => mktime(13, 45, 0, 10, 4, 2010))
+--EXPECT--
+04/10/2010 09:45:00 -04:00
+04/10/2010 09:45:00 -04:00
--- /dev/null
+--TEST--
+"range" function
+--TEMPLATE--
+{{ range(low=0+1, high=10+0, step=2)|join(',') }}
+--DATA--
+return array()
+--EXPECT--
+1,3,5,7,9
$tests[] = array($node, 'twig_number_format_filter($this->env, strtoupper("foo"), 2, ".", ",")');
}
+ // named arguments
+ $date = new Twig_Node_Expression_Constant(0, 1);
+ $node = $this->createFilter($date, 'date', array(
+ 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
+ 'format' => new Twig_Node_Expression_Constant('d/m/Y H:i:s P', 1),
+ ));
+ $tests[] = array($node, 'twig_date_format_filter($this->env, 0, "d/m/Y H:i:s P", "America/Chicago")');
+
+ // skip an optional argument
+ $date = new Twig_Node_Expression_Constant(0, 1);
+ $node = $this->createFilter($date, 'date', array(
+ 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
+ ));
+ $tests[] = array($node, 'twig_date_format_filter($this->env, 0, null, "America/Chicago")');
+
+ // underscores vs camelCase for named arguments
+ $string = new Twig_Node_Expression_Constant('abc', 1);
+ $node = $this->createFilter($string, 'reverse', array(
+ 'preserve_keys' => new Twig_Node_Expression_Constant(true, 1),
+ ));
+ $tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)');
+ $node = $this->createFilter($string, 'reverse', array(
+ 'preserveKeys' => new Twig_Node_Expression_Constant(true, 1),
+ ));
+ $tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)');
+
return $tests;
}
+ /**
+ * @expectedException Twig_Error_Syntax
+ * @expectedExceptionMessage Unknown argument "foobar" for filter "date".
+ */
+ public function testCompileWithWrongNamedArgumentName()
+ {
+ $date = new Twig_Node_Expression_Constant(0, 1);
+ $node = $this->createFilter($date, 'date', array(
+ 'foobar' => new Twig_Node_Expression_Constant('America/Chicago', 1),
+ ));
+
+ $compiler = $this->getCompiler();
+ $compiler->compile($node);
+ }
+
+ /**
+ * @expectedException Twig_Error_Syntax
+ * @expectedExceptionMessage Value for argument "from" is required for filter "replace".
+ */
+ public function testCompileWithMissingNamedArgument()
+ {
+ $value = new Twig_Node_Expression_Constant(0, 1);
+ $node = $this->createFilter($value, 'replace', array(
+ 'to' => new Twig_Node_Expression_Constant('foo', 1),
+ ));
+
+ $compiler = $this->getCompiler();
+ $compiler->compile($node);
+ }
+
protected function createFilter($node, $name, array $arguments = array())
{
$name = new Twig_Node_Expression_Constant($name, 1);
$node = $this->createFunction('foobar', array(new Twig_Node_Expression_Constant('bar', 1)));
$tests[] = array($node, 'foobar($this->env, $context, "bar")', $environment);
+ // named arguments
+ $node = $this->createFunction('date', array(
+ 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
+ 'date' => new Twig_Node_Expression_Constant(0, 1),
+ ));
+ $tests[] = array($node, 'twig_date_converter($this->env, 0, "America/Chicago")');
+
return $tests;
}