Allow to use a dynamic attribute on the . operator via ()

This commit is contained in:
Fabien Potencier
2024-09-12 08:45:21 +02:00
parent c6d6ef22ec
commit 333638e8e6
13 changed files with 164 additions and 48 deletions
+1
View File
@@ -1,5 +1,6 @@
# 3.15.0 (2024-XX-XX)
* Deprecate the `attribute` function; use the `.` notation and wrap the name with parenthesis instead
* Add support for argument unpackaging
* Add JSON support for the file extension escaping strategy
* Support Markup instances (and any other \Stringable) as dynamic mapping keys
+18 -2
View File
@@ -8,8 +8,24 @@ feature that was deprecated in Twig 3.x is removed in Twig 4.0).
Functions
---------
* The ``twig_test_iterable`` function is deprecated; use the native PHP
``is_iterable`` function instead.
* The ``twig_test_iterable`` function is deprecated; use the native PHP
``is_iterable`` function instead.
* The ``attribute`` function is deprecated as of Twig 3.15 and will be removed
in Twig 4.0. Use the ``.`` operator instead and wrap the name with
parenthesis:
.. code-block:: twig
{# before #}
{{ attribute(object, method) }}
{{ attribute(object, method, arguments) }}
{{ attribute(array, item) }}
{# after #}
{{ object.(method) }}
{{ object.(method)(arguments) }}
{{ array[item] }}
Extensions
----------
+5
View File
@@ -1,6 +1,11 @@
``attribute``
=============
.. warning::
The ``attribute`` filter is deprecated as of Twig 3.15. Use the ``.``
operator that now accepts any expression when wrapped with parenthesis.
The ``attribute`` function can be used to access a "dynamic" attribute of a
variable:
+21 -14
View File
@@ -774,6 +774,27 @@ The following operators don't fit into any of the other categories:
{{ user.name }}
After the ``.``, you can use any expression by wrapping it with parenthesis
``()``.
One use case is when the attribute contains special characters (like ``-``
that would be interpreted as the minus operator):
.. code-block:: twig
{# equivalent to the non-working user.first-name #}
{{ user.('first-name') }}
Another use case is when the attribute is "dynamic" (defined via a variable):
.. code-block:: twig
{{ user.(name) }}
{{ user.('get' ~ name) }}
Before Twig 3.15, use the :doc:`attribute <functions/attribute>` function
instead for the two previous use cases.
.. sidebar:: PHP Implementation
To resolve ``user.name`` to a PHP call, Twig uses the following algorithm
@@ -803,20 +824,6 @@ The following operators don't fit into any of the other categories:
* if not, and if ``strict_variables`` is ``false``, return ``null``;
* if not, throw an exception.
.. note::
If you want to access a dynamic attribute of a variable, use the
:doc:`attribute<functions/attribute>` function instead.
The ``attribute`` function is also useful when the attribute contains
special characters (like ``-`` that would be interpreted as the minus
operator):
.. code-block:: twig
{# equivalent to the non-working user.first-name #}
{{ attribute(user, 'first-name') }}
* ``?:``: The ternary operator:
.. code-block:: twig
+12
View File
@@ -492,6 +492,18 @@ class ExpressionParser
$arguments = new ArrayExpression([], $lineno);
$type = Template::ANY_CALL;
if ('.' == $token->getValue()) {
if ($stream->nextIf(Token::PUNCTUATION_TYPE, '(')) {
$arg = $this->parseExpression();
$stream->expect(Token::PUNCTUATION_TYPE, ')');
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
$type = Template::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
}
$token = $stream->next();
if (
Token::NAME_TYPE == $token->getType()
+6
View File
@@ -1966,6 +1966,12 @@ final class CoreExtension extends AbstractExtension
$fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = null) => null);
$args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args);
$src = $parser->getStream()->getSourceContext();
$dep = new DeprecatedCallableInfo('twig/twig', '3.15', 'The "attribute" function is deprecated, use the "." notation instead.');
$dep->setName('attribute');
$dep->setType('function');
$dep->triggerDeprecation($src->getPath() ?: $src->getName(), $line);
return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line);
}
}
@@ -0,0 +1,18 @@
--TEST--
"." notation with dynamic attributes
--TEMPLATE--
{{ obj.(method) }}
{{ array.(item) }}
{{ obj.("bar")("a", "b") }}
{{ obj.("bar")(...arguments) }}
{{ obj.(method) is defined ? 'ok' : 'ko' }}
{{ 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
bar
bar_a-b
bar_a-b
ok
ko
@@ -0,0 +1,37 @@
--TEST--
"attribute" function
--DEPRECATION--
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 2.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 3.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 4.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 5.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 6.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 7.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 8.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 9.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 10.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 11.
--TEMPLATE--
{{ attribute(obj, method) }}
{{ attribute(variable=obj, attribute=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(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
foo
bar
bar_a-b
bar_a-b
bar_a-b
bar_a-b
ok
ko
-26
View File
@@ -1,26 +0,0 @@
--TEST--
"attribute" function
--TEMPLATE--
{{ attribute(obj, method) }}
{{ attribute(variable=obj, attribute=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(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
foo
bar
bar_a-b
bar_a-b
bar_a-b
bar_a-b
ok
ko
@@ -0,0 +1,40 @@
--TEST--
"defined" support for attribute
--DEPRECATION--
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 2.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 3.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 4.
Since twig/twig 3.15: Twig Function "attribute" is deprecated; use "The "attribute" function is deprecated, use the "." notation instead." instead in index.twig at line 5.
--TEMPLATE--
{{ attribute(nested, "definedVar") is defined ? 'ok' : 'ko' }}
{{ attribute(nested, "undefinedVar") is not defined ? 'ok' : 'ko' }}
{{ attribute(nested, definedVarName) is defined ? 'ok' : 'ko' }}
{{ attribute(nested, undefinedVarName) is not defined ? 'ok' : 'ko' }}
--DATA--
return [
'nested' => [
'definedVar' => 'defined',
],
'definedVarName' => 'definedVar',
'undefinedVarName' => 'undefinedVar',
]
--EXPECT--
ok
ok
ok
ok
--DATA--
return [
'nested' => [
'definedVar' => 'defined',
],
'definedVarName' => 'definedVar',
'undefinedVarName' => 'undefinedVar',
]
--CONFIG--
return ['strict_variables' => false]
--EXPECT--
ok
ok
ok
ok
@@ -1,10 +1,10 @@
--TEST--
"defined" support for attribute
"defined" support for dynamic attribute
--TEMPLATE--
{{ attribute(nested, "definedVar") is defined ? 'ok' : 'ko' }}
{{ attribute(nested, "undefinedVar") is not defined ? 'ok' : 'ko' }}
{{ attribute(nested, definedVarName) is defined ? 'ok' : 'ko' }}
{{ attribute(nested, undefinedVarName) is not defined ? 'ok' : 'ko' }}
{{ nested.("definedVar") is defined ? 'ok' : 'ko' }}
{{ nested.("undefinedVar") is not defined ? 'ok' : 'ko' }}
{{ nested.(definedVarName) is defined ? 'ok' : 'ko' }}
{{ nested.(undefinedVarName) is not defined ? 'ok' : 'ko' }}
--DATA--
return [
'nested' => [
+1 -1
View File
@@ -76,7 +76,7 @@ class TemplateTest extends TestCase
['{{ array.a() }}', 'Impossible to invoke a method ("a") on a sequence/mapping in "%s" at line 1.'],
['{{ empty_array.a }}', 'Key "a" does not exist as the sequence/mapping is empty in "%s" at line 1.'],
['{{ array.a }}', 'Key "a" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
['{{ attribute(array, -10) }}', 'Key "-10" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
['{{ array.(-10) }}', 'Key "-10" for sequence/mapping with keys "foo" does not exist in "%s" at line 1.'],
['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()"/"hasa()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'],
['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()"/"hasmissing_method()" or "__call()" exist and have public access in class "Twig\Tests\TemplateArrayAccessObject" in "%s" at line 1.'],
['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.'],