From 7f00cb9d106784018ad48222c53ec4fc7ef2219d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Fri, 20 Mar 2015 00:17:33 +0100 Subject: [PATCH] Added support for unconsumed macro arguments --- doc/tags/macro.rst | 4 ++ doc/templates.rst | 6 +- lib/Twig/Node/Macro.php | 62 ++++++++++++++----- test/Twig/Tests/Fixtures/macros/varargs.test | 21 +++++++ .../Fixtures/macros/varargs_argument.test | 8 +++ test/Twig/Tests/Node/MacroTest.php | 11 +++- 6 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 test/Twig/Tests/Fixtures/macros/varargs.test create mode 100644 test/Twig/Tests/Fixtures/macros/varargs_argument.test diff --git a/doc/tags/macro.rst b/doc/tags/macro.rst index 11c115a081..7df7981e16 100644 --- a/doc/tags/macro.rst +++ b/doc/tags/macro.rst @@ -20,6 +20,10 @@ Macros differs from native PHP functions in a few ways: * Arguments of a macro are always optional. +* If more positional arguments are passed to the macro than accepted by the macro, + they end up in the special ``varargs`` variable as a list of values. This variable + is always available in the macro body. + But as with PHP functions, macros don't have access to the current template variables. diff --git a/doc/templates.rst b/doc/templates.rst index a60c1965f3..418e3db91f 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -93,7 +93,7 @@ access the variable attribute: don't put the braces around them. If a variable or attribute does not exist, you will receive a ``null`` value -when the ``strict_variables`` option is set to ``false``; alternatively, if ``strict_variables`` +when the ``strict_variables`` option is set to ``false``; alternatively, if ``strict_variables`` is set, Twig will throw an error (see :ref:`environment options`). .. sidebar:: Implementation @@ -541,6 +541,10 @@ macro call: {% endmacro %} +Inside macros, you have access to special variable ``varargs``. If more positional arguments +are passed to the macro than accepted by the macro, they end up in the special ``varargs`` variable +as a list of values. + .. _twig-expressions: Expressions diff --git a/lib/Twig/Node/Macro.php b/lib/Twig/Node/Macro.php index ab7e8d25a2..c6565db347 100644 --- a/lib/Twig/Node/Macro.php +++ b/lib/Twig/Node/Macro.php @@ -16,8 +16,19 @@ */ class Twig_Node_Macro extends Twig_Node { + const VARARGS_NAME = 'varargs'; + public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null) { + foreach ($arguments as $argumentName => $argument) { + if (self::VARARGS_NAME === $argumentName) { + throw new Twig_Error_Syntax(sprintf( + 'The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments', + self::VARARGS_NAME, $name, self::VARARGS_NAME + ), $argument->getLine()); + } + } + parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag); } @@ -30,7 +41,7 @@ public function compile(Twig_Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write(sprintf("public function get%s(", $this->getAttribute('name'))) + ->write(sprintf('public function get%s(', $this->getAttribute('name'))) ; $count = count($this->getNode('arguments')); @@ -46,36 +57,55 @@ public function compile(Twig_Compiler $compiler) } } + if (PHP_VERSION_ID >= 50600) { + if ($count) { + $compiler->raw(', '); + } + + $compiler->raw('...$__varargs__'); + } + $compiler ->raw(")\n") ->write("{\n") ->indent() ; - if (!count($this->getNode('arguments'))) { - $compiler->write("\$context = \$this->env->getGlobals();\n\n"); - } else { + $compiler + ->write("\$context = \$this->env->mergeGlobals(array(\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { $compiler - ->write("\$context = \$this->env->mergeGlobals(array(\n") - ->indent() + ->addIndentation() + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") ; + } - foreach ($this->getNode('arguments') as $name => $default) { - $compiler - ->write('') - ->string($name) - ->raw(' => $__'.$name.'__') - ->raw(",\n") - ; - } + $compiler + ->addIndentation() + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + if (PHP_VERSION_ID >= 50600) { + $compiler->raw("\$__varargs__,\n"); + } else { $compiler - ->outdent() - ->write("));\n\n") + ->raw('func_num_args() > ') + ->repr($count) + ->raw(' ? array_slice(func_get_args(), ') + ->repr($count) + ->raw(") : array(),\n") ; } $compiler + ->outdent() + ->write("));\n\n") ->write("\$blocks = array();\n\n") ->write("ob_start();\n") ->write("try {\n") diff --git a/test/Twig/Tests/Fixtures/macros/varargs.test b/test/Twig/Tests/Fixtures/macros/varargs.test new file mode 100644 index 0000000000..412c90fae4 --- /dev/null +++ b/test/Twig/Tests/Fixtures/macros/varargs.test @@ -0,0 +1,21 @@ +--TEST-- +macro with arbitrary arguments +--TEMPLATE-- +{% from _self import test1, test2 %} + +{% macro test1(var) %} + {{- var }}: {{ varargs|join(", ") }} +{% endmacro %} + +{% macro test2() %} + {{- varargs|join(", ") }} +{% endmacro %} + +{{ test1("foo", "bar", "foobar") }} +{{ test2("foo", "bar", "foobar") }} +--DATA-- +return array(); +--EXPECT-- +foo: bar, foobar + +foo, bar, foobar diff --git a/test/Twig/Tests/Fixtures/macros/varargs_argument.test b/test/Twig/Tests/Fixtures/macros/varargs_argument.test new file mode 100644 index 0000000000..b08c8ec91f --- /dev/null +++ b/test/Twig/Tests/Fixtures/macros/varargs_argument.test @@ -0,0 +1,8 @@ +--TEST-- +macro with varargs argument +--TEMPLATE-- +{% macro test(varargs) %} +{% endmacro %} +--EXCEPTION-- +Twig_Error_Syntax: The argument "varargs" in macro "test" cannot be defined because the variable "varargs" is reserved for arbitrary arguments in "index.twig" at line 2 + diff --git a/test/Twig/Tests/Node/MacroTest.php b/test/Twig/Tests/Node/MacroTest.php index 52ee8b7c40..901e57b91d 100644 --- a/test/Twig/Tests/Node/MacroTest.php +++ b/test/Twig/Tests/Node/MacroTest.php @@ -31,14 +31,23 @@ public function getTests() ), array(), 1); $node = new Twig_Node_Macro('foo', $body, $arguments, 1); + if (PHP_VERSION_ID >= 50600) { + $declaration = ', ...$__varargs__'; + $varargs = '$__varargs__'; + } else { + $declaration = ''; + $varargs = 'func_num_args() > 2 ? array_slice(func_get_args(), 2) : array()'; + } + return array( array($node, <<env->mergeGlobals(array( "foo" => \$__foo__, "bar" => \$__bar__, + "varargs" => $varargs, )); \$blocks = array();