Add support for named arguments on special functions

This commit is contained in:
Fabien Potencier
2024-08-05 14:23:35 +02:00
parent 26bcadeaeb
commit 0c30e78b1c
9 changed files with 48 additions and 13 deletions
+1
View File
@@ -1,5 +1,6 @@
# 3.12.0 (2024-XX-XX)
* Add support for named arguments to the `block`, and `attribute` functions
* Throw a SyntaxError exception at compile time when a Twig callable has not the minimum number of required arguments
* Add a `CallableArgumentsExtractor` class
* Deprecate passing a name to `FunctionExpression`, `FilterExpression`, and `TestExpression`;
+7
View File
@@ -21,3 +21,10 @@ attribute:
The resolution algorithm is the same as the one used for the ``.``
notation, except that the item can be any valid expression.
Arguments
---------
* ``variable``: The variable
* ``attribute``: The attribute name
* ``arguments``: An array of arguments to pass to the call
+6
View File
@@ -32,6 +32,12 @@ current template:
...
{% endif %}
Arguments
---------
* ``name``: The block name
* ``template``: The template where to look for the block
.. seealso::
:doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
+13 -11
View File
@@ -33,6 +33,7 @@ use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
use Twig\Node\Node;
use Twig\Util\CallableArgumentsExtractor;
/**
* Parses expressions.
@@ -458,7 +459,6 @@ class ExpressionParser
{
switch ($name) {
case 'parent':
$this->parseArguments();
if (!\count($this->parser->getBlockStack())) {
throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
@@ -467,21 +467,23 @@ class ExpressionParser
throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
$this->parseArguments(true);
return new ParentExpression($this->parser->peekBlockStack(), $line);
case 'block':
$args = $this->parseArguments();
if (\count($args) < 1) {
throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
}
$fakeNode = new Node(lineno: $line);
$fakeNode->setSourceContext($this->parser->getStream()->getSourceContext());
$fakeFunction = new TwigFunction('block', fn ($name, $template = null) => null);
$args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($this->parseArguments(true));
return new BlockReferenceExpression($args->getNode('0'), \count($args) > 1 ? $args->getNode('1') : null, $line);
return new BlockReferenceExpression($args[0], $args[1] ?? null, $line);
case 'attribute':
$args = $this->parseArguments();
if (\count($args) < 2) {
throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
}
$fakeNode = new Node(lineno: $line);
$fakeNode->setSourceContext($this->parser->getStream()->getSourceContext());
$fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = []) => null);
$args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($this->parseArguments(true));
return new GetAttrExpression($args->getNode('0'), $args->getNode('1'), \count($args) > 2 ? $args->getNode('2') : null, Template::ANY_CALL, $line);
return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line);
default:
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new ArrayExpression([], $line);
+4
View File
@@ -2,17 +2,21 @@
"attribute" function
--TEMPLATE--
{{ attribute(obj, method) }}
{{ attribute(variable=obj, attribute=method) }}
{{ attribute(array, item) }}
{{ attribute(obj, "bar", ["a", "b"]) }}
{{ attribute(obj, "bar", arguments) }}
{{ attribute(variable=obj, attribute="bar", arguments=arguments) }}
{{ attribute(obj, method) is defined ? 'ok' : 'ko' }}
{{ attribute(obj, nonmethod) is defined ? 'ok' : 'ko' }}
--DATA--
return ['obj' => new Twig\Tests\TwigTestFoo(), 'method' => 'foo', 'array' => ['foo' => 'bar'], 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => ['a', 'b']]
--EXPECT--
foo
foo
bar
bar_a-b
bar_a-b
bar_a-b
ok
ko
@@ -0,0 +1,8 @@
--TEST--
"attribute" function
--TEMPLATE--
{{ attribute(var=var, template="tpl") }}
--DATA--
return ['var' => null]
--EXCEPTION--
Twig\Error\SyntaxError: Value for argument "variable" is required for function "attribute" in "index.twig" at line 2.
+2 -1
View File
@@ -5,8 +5,9 @@
{% block bar %}BAR{% endblock %}
--TEMPLATE(base.twig)--
{% block foo %}{{ block('bar') }}{% endblock %}
{% block baz %}{{ block(name='bar') }}{% endblock %}
{% block bar %}BAR_BASE{% endblock %}
--DATA--
return []
--EXPECT--
BARBAR
BARBARBAR
@@ -6,6 +6,10 @@
{{ block('foo', included_loaded_internal) }}
{% set output = block('foo', 'included.twig') %}
{{ output }}
{% set output = block(name='foo', template='included.twig') %}
{{ output }}
{% set output = block(template='included.twig', name='foo') %}
{{ output }}
{% block foo %}NOT FOO{% endblock %}
--TEMPLATE(included.twig)--
{% block foo %}FOO{% endblock %}
@@ -19,4 +23,6 @@ FOO
FOO
FOO
FOO
FOO
FOO
NOT FOO
@@ -9,4 +9,4 @@
--DATA--
return []
--EXCEPTION--
Twig\Error\SyntaxError: The "block" function takes one argument (the block name) in "base.twig" at line 2.
Twig\Error\SyntaxError: Value for argument "name" is required for function "block" in "base.twig" at line 2.