mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-27 10:27:26 +00:00
Allow to use a dynamic attribute on the . operator via ()
This commit is contained in:
@@ -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
@@ -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
|
||||
----------
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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.'],
|
||||
|
||||
Reference in New Issue
Block a user