* fixed the filesystem loader cache when a template name exists in several namespaces
* fixed template_from_string when the template includes or extends other ones
* fixed a crash of the C extension on an edge case
- * in the template can be directly called macros defined in the same template
- * added support for named arguments for macros
- * fixed fatal error that should be an exception when macro does not exist in template
* 1.13.2 (2013-08-03)
``macro``
=========
-.. versionadded:: 1.14
- Support for default argument values was added in Twig 1.14.
-
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.
Macros differs from native PHP functions in a few ways:
-* Arguments of a macro are always optional;
-
-* Default argument values may be defined by using the ``default`` filter in the
- macro body.
-
-.. tip::
-
- A macro may define default values for scalar arguments as follows:
-
- .. code-block:: jinja
+* Default argument values are defined by using the ``default`` filter in the
+ macro body;
- {% macro input(name, value = "", type = "text", size = 20) %}
- <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
- {% endmacro %}
+* Arguments of a macro are always optional.
But as with PHP functions, macros don't have access to the current template
variables.
<p>{{ forms.input('username') }}</p>
<p>{{ forms.input('password', null, 'password') }}</p>
- <p>{{ forms.input(name='username', size=40) }}</p>
If macros are defined and used in the same template, you can use the
special ``_self`` variable to import them:
Go to the :doc:`functions<functions/index>` page to learn more about the
built-in functions.
-.. _named_arguments:
-
Named Arguments
---------------
.. versionadded:: 1.12
Support for named arguments was added in Twig 1.12.
-.. versionadded:: 1.14
- Support for named arguments for macros was added in Twig 1.14.
-
-Arguments for filters, functions and macros can also be passed as *named arguments*:
-
.. code-block:: jinja
{% for i in range(low=1, high=10, step=2) %}
.. versionadded:: 1.12
Support for default argument values was added in Twig 1.12.
-.. versionadded:: 1.14
- Support for macro call with named arguments was added in Twig 1.14.
- Support for directly call macros defined in the same template was added in Twig 1.14.
-
Macros are comparable with functions in regular programming languages. They
are useful to reuse often used HTML fragments to not repeat yourself.
<p>{{ forms.input('username') }}</p>
-Macros defined in the same template can be directly called:
-
-.. code-block:: jinja
-
- {% macro submit(name) %}
- <input type="submit" value="{{ name }}" />
- {% endmacro %}
-
- <p>{{ submit('Send') }}</p>
-
-.. note::
-
- If the macro name matches the name of a function, it need to be "imported"
- via the :doc:`import<tags/import>`.
-
Alternatively, you can import individual macro names from a template into the
current namespace via the :doc:`from<tags/from>` tag and optionally alias them:
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
{% endmacro %}
-Arguments for macro can also be passed as :ref:`named arguments<named_arguments>`:
-
-.. code-block:: jinja
-
- {% import "forms.html" as forms %}
-
- <p>{{ forms.input(name='username', size=40) }}</p>
-
.. _twig-expressions:
Expressions
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_Template::ANY_CALL, $line);
default:
- $args = $this->parseArguments(true);
- if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) {
- return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $this->createArrayFromArguments($args), $line);
- }
-
- try {
- $class = $this->getFunctionNodeClass($name, $line);
- } catch (Twig_Error_Syntax $e) {
- if (!$this->parser->hasMacro($name)) {
- throw $e;
+ if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
+ $arguments = new Twig_Node_Expression_Array(array(), $line);
+ foreach ($this->parseArguments() as $n) {
+ $arguments->addElement($n);
}
- return new Twig_Node_Expression_MacroCall(new Twig_Node_Expression_Name('_self', $line), $name, $this->createArrayFromArguments($args), $line);
+ $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
+ $node->setAttribute('safe', true);
+
+ return $node;
}
+ $args = $this->parseArguments(true);
+ $class = $this->getFunctionNodeClass($name, $line);
+
return new $class($name, $args, $line);
}
}
($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
+
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+ $type = Twig_TemplateInterface::METHOD_CALL;
+ foreach ($this->parseArguments() as $n) {
+ $arguments->addElement($n);
+ }
+ }
} else {
throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
}
- $arguments = $this->createArrayFromArguments($this->parseArguments(true));
-
- return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno);
- }
+ $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
+ $node->setAttribute('safe', true);
- if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
- $type = Twig_Template::METHOD_CALL;
- $arguments = $this->createArrayFromArguments($this->parseArguments());
+ return $node;
}
} else {
$type = Twig_Template::ARRAY_CALL;
*
* @param Boolean $namedArguments Whether to allow named arguments or not
* @param Boolean $definition Whether we are parsing arguments for a function definition
- *
- * @return Twig_Node
*/
public function parseArguments($namedArguments = false, $definition = false)
{
}
}
- if ($definition && null === $name) {
- $name = $value->getAttribute('name');
- $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
- }
-
- if (null === $name) {
- $args[] = $value;
- } else {
+ if ($definition) {
+ if (null === $name) {
+ $name = $value->getAttribute('name');
+ $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
+ }
$args[$name] = $value;
+ } else {
+ 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');
return true;
}
-
- private function createArrayFromArguments(Twig_Node $arguments, $line = null)
- {
- $line = null === $line ? $arguments->getLine() : $line;
- $array = new Twig_Node_Expression_Array(array(), $line);
- foreach ($arguments as $key => $value) {
- $array->addElement($value, new Twig_Node_Expression_Constant($key, $value->getLine()));
- }
-
- return $array;
- }
}
+++ /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 macro call node.
- *
- * @author Martin HasoĊ <martin.hason@gmail.com>
- */
-class Twig_Node_Expression_MacroCall extends Twig_Node_Expression
-{
- public function __construct(Twig_Node_Expression $template, $name, Twig_Node_Expression_Array $arguments, $lineno)
- {
- parent::__construct(array('template' => $template, 'arguments' => $arguments), array('name' => $name), $lineno);
- }
-
- public function compile(Twig_Compiler $compiler)
- {
- $namedNames = array();
- $namedCount = 0;
- $positionalCount = 0;
- foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
- $name = $pair['key']->getAttribute('value');
- if (!is_int($name)) {
- $namedCount++;
- $namedNames[$name] = 1;
- } elseif ($namedCount > 0) {
- throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for macro "%s".', $this->getAttribute('name')), $this->lineno);
- } else {
- $positionalCount++;
- }
- }
-
- $compiler
- ->raw('$this->callMacro(')
- ->subcompile($this->getNode('template'))
- ->raw(', ')->repr($this->getAttribute('name'))
- ->raw(', ')->subcompile($this->getNode('arguments'))
- ;
-
- if ($namedCount > 0) {
- $compiler
- ->raw(', ')->repr($namedNames)
- ->raw(', ')->repr($namedCount)
- ->raw(', ')->repr($positionalCount)
- ;
- }
-
- $compiler
- ->raw(')')
- ;
- }
-}
{
public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
{
- parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name, 'method' => 'get'.ucfirst($name)), $lineno, $tag);
+ parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
}
/**
{
$compiler
->addDebugInfo($this)
- ->write(sprintf("public function %s(", $this->getAttribute('method')))
+ ->write(sprintf("public function get%s(", $this->getAttribute('name')))
;
$count = count($this->getNode('arguments'));
$compiler
->outdent()
- ->write(");\n\n")
- ;
-
- // macro information
- $compiler
- ->write("\$this->macros = array(\n")
- ->indent()
- ;
-
- foreach ($this->getNode('macros') as $name => $node) {
- $compiler
- ->addIndentation()->repr($name)->raw(" => array(\n")
- ->indent()
- ->write("'method' => ")->repr($node->getAttribute('method'))->raw(",\n")
- ->write("'default_argument_values' => array(\n")
- ->indent()
- ;
- foreach ($node->getNode('arguments') as $argument => $value) {
- $compiler->addIndentation()->repr($argument)->raw (' => ')->subcompile($value)->raw(",\n");
- }
- $compiler
- ->outdent()
- ->write(")\n")
- ->outdent()
- ->write("),\n")
- ;
- }
- $compiler
- ->outdent()
->write(");\n")
- ;
-
- $compiler
->outdent()
- ->write("}\n\n")
+ ->write("}\n\n");
;
}
} else {
$this->setSafe($node, array());
}
- } elseif ($node instanceof Twig_Node_Expression_MacroCall) {
- $this->setSafe($node, array('all'));
} elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) {
$name = $node->getNode('node')->getAttribute('name');
// attributes on template instances are safe
protected $env;
protected $blocks;
protected $traits;
- protected $macros;
/**
* Constructor.
$this->env = $env;
$this->blocks = array();
$this->traits = array();
- $this->macros = array();
}
/**
}
/**
- * Calls macro in a template.
- *
- * @param Twig_Template $template The template
- * @param string $macro The name of macro
- * @param array $arguments The arguments of macro
- * @param array $namedNames An array of names of arguments as keys
- * @param integer $namedCount The count of named arguments
- * @param integer $positionalCount The count of positional arguments
- *
- * @return string The content of a macro
- *
- * @throws Twig_Error_Runtime if the macro is not defined
- * @throws Twig_Error_Runtime if the argument is defined twice
- * @throws Twig_Error_Runtime if the argument is unknown
- */
- protected function callMacro(Twig_Template $template, $macro, array $arguments, array $namedNames = array(), $namedCount = 0, $positionalCount = -1)
- {
- if (!isset($template->macros[$macro]['reflection'])) {
- if (!isset($template->macros[$macro])) {
- throw new Twig_Error_Runtime(sprintf('Macro "%s" is not defined in the template "%s".', $macro, $template->getTemplateName()));
- }
-
- $template->macros[$macro]['reflection'] = new ReflectionMethod($template, $template->macros[$macro]['method']);
- }
-
- if ($namedCount < 1) {
- return $template->macros[$macro]['reflection']->invokeArgs($template, $arguments);
- }
-
- $i = 0;
- $args = array();
- foreach ($template->macros[$macro]['default_argument_values'] as $name => $value) {
- if (isset($namedNames[$name])) {
- if ($i < $positionalCount) {
- throw new Twig_Error_Runtime(sprintf('Argument "%s" is defined twice for macro "%s" defined in the template "%s".', $name, $macro, $template->getTemplateName()));
- }
-
- $args[] = $arguments[$name];
- if (--$namedCount < 1) {
- break;
- }
- } elseif ($i < $positionalCount) {
- $args[] = $arguments[$i];
- } else {
- $args[] = $value;
- }
-
- $i++;
- }
-
- if ($namedCount > 0) {
- $parameters = array_keys(array_diff_key($namedNames, $template->macros[$macro]['default_argument_values']));
- throw new Twig_Error_Runtime(sprintf('Unknown argument%s "%s" for macro "%s" defined in the template "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', $parameters), $macro, $template->getTemplateName()));
- }
-
- return $template->macros[$macro]['reflection']->invokeArgs($template, $args);
- }
-
- /**
* This method is only useful when testing Twig. Do not use it.
*/
public static function clearCache()
$node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
foreach ($targets as $name => $alias) {
- $this->parser->addImportedSymbol('macro', $alias, $name, $node->getNode('var'));
+ $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
}
return $node;
}
/**
+ * @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 An argument must be a name. Unexpected token "string" of value "a" ("name" expected) in "index" at line 1
*/
+++ /dev/null
---TEST--
-Exception for unknown macro
---TEMPLATE--
-{% import _self as macros %}
-{{ macros.foo() }}
---DATA--
-return array()
---EXCEPTION--
-Twig_Error_Runtime: Macro "foo" is not defined in the template "index.twig" in "index.twig" at line 3.
+++ /dev/null
---TEST--
-Exception for unknown macro in different template
---TEMPLATE--
-{% import foo_template as macros %}
-{{ macros.foo() }}
---TEMPLATE(foo.twig)--
-foo
---DATA--
-return array('foo_template' => 'foo.twig')
---EXCEPTION--
-Twig_Error_Runtime: Macro "foo" is not defined in the template "foo.twig" in "index.twig" at line 3.
+++ /dev/null
---TEST--
-Auto importing macros in the same file
---TEMPLATE--
-{% macro test(a) %}
-{{ a }}
-{%- endmacro %}
-
-{% macro foo(a) %}
-foo: {{ test(a) }}
-{%- endmacro %}
-
-{{ test('foo') }}
-{{ foo('foo') }}
-{{ test(a = 'bar') }}
---DATA--
-return array();
---EXPECT--
-foo
-foo: foo
-bar
+++ /dev/null
---TEST--
-macro with named arguments
---TEMPLATE--
-{% import _self as test %}
-{% from _self import test %}
-
-{% macro test(a, b = 'bar') -%}
-{{ a }}{{ b }}
-{%- endmacro %}
-
-{{ test(b = 'bar', a = 'foo') }}
-{{ test(b = '2', a = 'foo') }}
-{{ test('bar', b = 'foo') }}
-{{ test.test(b = 1) }}
-{{ test.test(b = 'foo') }}
-{{ test.test(2, b = 'foo') }}
-{{ test.test(3, b = 'foobar') }}
-{{ test.test(a = 1) }}
-{{ test.test(a = 2) }}
---DATA--
-return array();
---EXPECT--
-foobar
-foo2
-barfoo
-1
-foo
-2foo
-3foobar
-1bar
-2bar
+++ /dev/null
-<?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_Node_Expression_MacroCallTest extends PHPUnit_Framework_TestCase
-{
- /**
- * @expectedException Twig_Error_Syntax
- * @expectedExceptionMessage Positional arguments cannot be used after named arguments for macro "foo".
- */
- public function testGetArgumentsWhenPositionalArgumentsAfterNamedArguments()
- {
- $arguments = new Twig_Node_Expression_Array(array(new Twig_Node_Expression_Constant('named', -1), $this->getMock('Twig_Node'), new Twig_Node_Expression_Constant(0, -1), $this->getMock('Twig_Node')), -1);
- $node = new Twig_Node_Expression_MacroCall($this->getMock('Twig_Node_Expression'), 'foo', $arguments, -1);
- $node->compile($this->getMock('Twig_Compiler', null, array(), '', false));
- }
-}
return array(
array($node, <<<EOF
// line 1
-public function getFoo(\$_foo = null, \$_bar = "Foo")
+public function getfoo(\$_foo = null, \$_bar = "Foo")
{
\$context = \$this->env->mergeGlobals(array(
"foo" => \$_foo,
\$this->blocks = array(
);
-
- \$this->macros = array(
- );
}
protected function doDisplay(array \$context, array \$blocks = array())
public function getDebugInfo()
{
- return array ( 22 => 1,);
+ return array ( 19 => 1,);
}
}
EOF
\$this->blocks = array(
);
-
- \$this->macros = array(
- );
}
protected function doGetParent(array \$context)
public function getDebugInfo()
{
- return array ( 27 => 1,);
+ return array ( 24 => 1,);
}
}
EOF
\$this->blocks = array(
);
-
- \$this->macros = array(
- );
}
protected function doDisplay(array \$context, array \$blocks = array())
public function getDebugInfo()
{
- return array ( 23 => 1,);
+ return array ( 20 => 1,);
}
}
EOF
\$this->blocks = array(
);
-
- \$this->macros = array(
- );
}
protected function doGetParent(array \$context)
return $tests;
}
-
- /**
- * @expectedException Twig_Error_Runtime
- * @expectedExceptionMessage Macro "foo" is not defined in the template "my/template".
- */
- public function testCallUnknownMacro()
- {
- $template = new Twig_TemplateTest($this->getMock('Twig_Environment'));
- $template->callMacro(new Twig_Tests_TemplateWithMacros('my/template'), 'foo', array());
- }
-
- /**
- * @expectedException Twig_Error_Runtime
- * @expectedExceptionMessage Argument "format" is defined twice for macro "date" defined in the template "my/template".
- */
- public function testCallMacroWhenArgumentIsDefinedTwice()
- {
- $template = new Twig_TemplateTest($this->getMock('Twig_Environment'));
- $template->callMacro(new Twig_Tests_TemplateWithMacros('my/template', array('date' => array(
- 'method' => 'getDate',
- 'default_argument_values' => array('format' => null, 'template' => null)
- ))), 'date', array('d', 'format' => 'H'), array('format' => 1), 1, 1);
- }
-
- /**
- * @expectedException Twig_Error_Runtime
- * @expectedExceptionMessage Unknown argument "unknown" for macro "date" defined in the template "my/template".
- */
- public function testCallMacroWithWrongNamedArgumentName()
- {
- $template = new Twig_TemplateTest($this->getMock('Twig_Environment'));
- $template->callMacro(new Twig_Tests_TemplateWithMacros('my/template', array('date' => array(
- 'method' => 'getDate',
- 'default_argument_values' => array('foo' => 1, 'bar' => 2)
- ))), 'date', array('foo' => 2), array('foo' => 1, 'unknown' => 1), 2, 0);
- }
-
- /**
- * @expectedException Twig_Error_Runtime
- * @expectedExceptionMessage Unknown arguments "unknown1", "unknown2" for macro "date" defined in the template "my/template".
- */
- public function testCallMacroWithWrongNamedArgumentNames()
- {
- $template = new Twig_TemplateTest($this->getMock('Twig_Environment'));
- $template->callMacro(new Twig_Tests_TemplateWithMacros('my/template', array('date' => array(
- 'method' => 'getDate',
- 'default_argument_values' => array()
- ))), 'date', array(), array('unknown1' => 1, 'unknown2' => 2), 2, 0);
- }
}
class Twig_TemplateTest extends Twig_Template
return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
}
}
-
- public function callMacro(Twig_Template $template, $macro, array $arguments, array $namedNames = array(), $namedCount = 0, $positionalCount = -1)
- {
- return parent::callMacro($template, $macro, $arguments, $namedNames, $namedCount, $positionalCount);
- }
-}
-
-class Twig_Tests_TemplateWithMacros extends Twig_Template
-{
- protected $name;
-
- public function __construct($name, array $macros = array())
- {
- $this->name = $name;
- $this->macros = $macros;
- }
-
- public function getTemplateName()
- {
- return $this->name;
- }
-
- public function getDate()
- {
- }
-
- protected function doDisplay(array $context, array $blocks = array())
- {
- }
}
class Twig_TemplateArrayAccessObject implements ArrayAccess