mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-15 11:56:50 +00:00
Added support for named arguments for macros
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
* 1.13.2 (2013-XX-XX)
|
||||
|
||||
* added support for named arguments for macros
|
||||
* fixed fatal error that should be an exception when macro does not exist in template
|
||||
|
||||
* 1.13.1 (2013-06-06)
|
||||
|
||||
+17
-3
@@ -1,6 +1,9 @@
|
||||
``macro``
|
||||
=========
|
||||
|
||||
.. versionadded:: 1.12
|
||||
Support for default argument values was added in Twig 1.12.
|
||||
|
||||
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.
|
||||
@@ -15,10 +18,20 @@ Here is a small example of a macro that renders a form element:
|
||||
|
||||
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;
|
||||
|
||||
* 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
|
||||
|
||||
{% macro input(name, value = "", type = "text", size = 20) %}
|
||||
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
|
||||
{% endmacro %}
|
||||
|
||||
But as with PHP functions, macros don't have access to the current template
|
||||
variables.
|
||||
@@ -46,6 +59,7 @@ The macro can then be called at will:
|
||||
|
||||
<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:
|
||||
|
||||
+17
-1
@@ -191,13 +191,18 @@ progression of integers:
|
||||
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.
|
||||
|
||||
Arguments for filters and functions can also be passed as *named arguments*:
|
||||
.. versionadded:: 1.13.2
|
||||
Support for named arguments for macros was added in Twig 1.13.2.
|
||||
|
||||
Arguments for filters, functions and macros can also be passed as *named arguments*:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
@@ -498,6 +503,9 @@ Macros
|
||||
.. versionadded:: 1.12
|
||||
Support for default argument values was added in Twig 1.12.
|
||||
|
||||
.. versionadded:: 1.13.2
|
||||
Support for macro call with named arguments was added in Twig 1.13.2.
|
||||
|
||||
Macros are comparable with functions in regular programming languages. They
|
||||
are useful to reuse often used HTML fragments to not repeat yourself.
|
||||
|
||||
@@ -542,6 +550,14 @@ macro call:
|
||||
<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
|
||||
|
||||
@@ -318,13 +318,11 @@ class Twig_ExpressionParser
|
||||
|
||||
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_TemplateInterface::ANY_CALL, $line);
|
||||
default:
|
||||
$args = $this->parseArguments(true);
|
||||
if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) {
|
||||
$arguments = $this->createArrayFromArguments($this->parseArguments());
|
||||
|
||||
return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $arguments, $line);
|
||||
return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $this->createArrayFromArguments($args), $line);
|
||||
}
|
||||
|
||||
$args = $this->parseArguments(true);
|
||||
$class = $this->getFunctionNodeClass($name, $line);
|
||||
|
||||
return new $class($name, $args, $line);
|
||||
@@ -357,7 +355,7 @@ class Twig_ExpressionParser
|
||||
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());
|
||||
$arguments = $this->createArrayFromArguments($this->parseArguments(true));
|
||||
|
||||
return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno);
|
||||
}
|
||||
|
||||
@@ -23,13 +23,37 @@ class Twig_Node_Expression_MacroCall extends Twig_Node_Expression
|
||||
|
||||
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'))
|
||||
->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(')')
|
||||
;
|
||||
}
|
||||
|
||||
@@ -249,6 +249,15 @@ class Twig_Node_Module extends Twig_Node
|
||||
->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")
|
||||
;
|
||||
|
||||
+40
-5
@@ -450,15 +450,20 @@ abstract class Twig_Template implements Twig_TemplateInterface
|
||||
/**
|
||||
* 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 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)
|
||||
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])) {
|
||||
@@ -468,7 +473,37 @@ abstract class Twig_Template implements Twig_TemplateInterface
|
||||
$template->macros[$macro]['reflection'] = new ReflectionMethod($template, $template->macros[$macro]['method']);
|
||||
}
|
||||
|
||||
return $template->macros[$macro]['reflection']->invokeArgs($template, $arguments);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -226,17 +226,6 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
|
||||
$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 An argument must be a name. Unexpected token "string" of value "a" ("name" expected) in "index" at line 1
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
--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
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
@@ -371,6 +371,45 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
|
||||
$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
|
||||
@@ -430,9 +469,9 @@ class Twig_TemplateTest extends Twig_Template
|
||||
}
|
||||
}
|
||||
|
||||
public function callMacro(Twig_Template $template, $macro, array $arguments)
|
||||
public function callMacro(Twig_Template $template, $macro, array $arguments, array $namedNames = array(), $namedCount = 0, $positionalCount = -1)
|
||||
{
|
||||
return parent::callMacro($template, $macro, $arguments);
|
||||
return parent::callMacro($template, $macro, $arguments, $namedNames, $namedCount, $positionalCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user