mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-31 20:47:28 +00:00
Assignment operator array destructuring
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# 3.23.0 (2026-XX-XX)
|
||||
|
||||
* Add `=` assignment operator (allows to set variables in expression or to replace the short-form of the set tag)
|
||||
* Add array destructuring
|
||||
* Add `?.` null-safe operator
|
||||
* Add `===` and `!==` operators (equivalent to the `same as` and `not same as` tests)
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ The assigned value can be any valid :ref:`Twig expression
|
||||
.. tip::
|
||||
|
||||
To assign a value within an expression, use the :ref:`= operator
|
||||
<assignment-operator>`:
|
||||
<templates-assignment-operator>`:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
|
||||
+49
-12
@@ -132,30 +132,24 @@ Setting Variables
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can assign values to variables inside code blocks using either the
|
||||
:doc:`set<tags/set>` tag or the :ref:`= operator <assignment-operator>`:
|
||||
:doc:`set<tags/set>` tag or the :ref:`= operator <templates-assignment-operator>`:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set name = 'Fabien' %}
|
||||
{% set numbers = [1, 2] %}
|
||||
{% set map = {'city': 'Paris'} %}
|
||||
{% set first, last = 'Fabien', 'Potencier' %}
|
||||
|
||||
{# or #}
|
||||
|
||||
{% do name = 'Fabien' %}
|
||||
{% do numbers = [1, 2] %}
|
||||
{% do map = {'city': 'Paris'} %}
|
||||
{% do [first, last] = ['Fabien', 'Potencier'] %}
|
||||
|
||||
For simple assignments, both are equivalent. However, the ``set`` tag provides
|
||||
additional features:
|
||||
|
||||
* **Multi-target assignment**: Assign multiple variables at once:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set first, last = 'Fabien', 'Potencier' %}
|
||||
|
||||
* **Block capture**: Capture chunks of template content into a variable:
|
||||
The ``set`` tag can also be used to capture template content into
|
||||
a variable:
|
||||
|
||||
.. code-block:: html+twig
|
||||
|
||||
@@ -1019,7 +1013,7 @@ The following operators don't fit into any of the other categories:
|
||||
Support for expanding the arguments of a function call was introduced in
|
||||
Twig 3.15.
|
||||
|
||||
.. _assignment-operator:
|
||||
.. _templates-assignment-operator:
|
||||
|
||||
* ``=``: The assignment operator assigns a value to a variable within an
|
||||
expression:
|
||||
@@ -1038,6 +1032,9 @@ The following operators don't fit into any of the other categories:
|
||||
{# assignment can be used inside other expressions #}
|
||||
{% do a = (b = 4) + 5 %}
|
||||
|
||||
The assignment operator also supports :ref:`destructuring
|
||||
<templates-destructuring>`.
|
||||
|
||||
.. versionadded:: 3.23
|
||||
|
||||
The ``=`` assignment operator was added in Twig 3.23.
|
||||
@@ -1109,6 +1106,46 @@ parentheses:
|
||||
{# use parenthesis to change precedence #}
|
||||
{{ (greeting ~ name)|lower }} {# hello fabien #}
|
||||
|
||||
.. _templates-destructuring:
|
||||
|
||||
Destructuring
|
||||
-------------
|
||||
|
||||
Destructuring allows you to extract values from arrays and assign them to
|
||||
variables in a single operation using the ``=`` :ref:`assignment operator
|
||||
<templates-assignment-operator>`.
|
||||
|
||||
.. versionadded:: 3.23
|
||||
|
||||
Destructuring was added in Twig 3.23.
|
||||
|
||||
Array Destructuring
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use square brackets on the left side of an assignment to destructure an array:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% do [first, last] = ['Fabien', 'Potencier'] %}
|
||||
|
||||
{{ first }} {# Fabien #}
|
||||
{{ last }} {# Potencier #}
|
||||
|
||||
If there are more variables than values, the extra variables are set to
|
||||
``null``:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{# extra will be null #}
|
||||
{% do [first, last, extra] = ['Fabien', 'Potencier'] %}
|
||||
|
||||
You can skip values by leaving a slot empty:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{# only assign the second value #}
|
||||
{% do [, last] = ['Fabien', 'Potencier'] %}
|
||||
|
||||
.. _templates-whitespace-control:
|
||||
|
||||
Whitespace Control
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace Twig\ExpressionParser\Infix;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\ExpressionParser\InfixAssociativity;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Binary\DestructuringSetBinary;
|
||||
use Twig\Node\Expression\Binary\SetBinary;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Parser;
|
||||
@@ -36,7 +38,7 @@ class AssignmentExpressionParser extends BinaryOperatorExpressionParser
|
||||
*/
|
||||
public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression
|
||||
{
|
||||
if (!$left instanceof ContextVariable) {
|
||||
if (!$left instanceof ContextVariable && !$left instanceof ArrayExpression) {
|
||||
throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned.', $left::class), $token->getLine(), $parser->getStream()->getSourceContext());
|
||||
}
|
||||
$right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence());
|
||||
@@ -45,7 +47,11 @@ class AssignmentExpressionParser extends BinaryOperatorExpressionParser
|
||||
default => throw new \LogicException(\sprintf('Unknown operator: %s.', $this->getName())),
|
||||
};
|
||||
|
||||
return new SetBinary($left, $right, $token->getLine());
|
||||
if ($left instanceof ArrayExpression) {
|
||||
return new DestructuringSetBinary($left, $right, $token->getLine());
|
||||
} else {
|
||||
return new SetBinary($left, $right, $token->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
|
||||
@@ -20,6 +20,7 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Binary\ConcatBinary;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\EmptyExpression;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Parser;
|
||||
use Twig\Token;
|
||||
@@ -170,7 +171,12 @@ final class LiteralExpressionParser extends AbstractExpressionParser implements
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$node->addElement($parser->parseExpression());
|
||||
// Check for empty slots (comma with no expression)
|
||||
if ($stream->test(Token::PUNCTUATION_TYPE, ',')) {
|
||||
$node->addElement(new EmptyExpression($stream->getCurrent()->getLine()));
|
||||
} else {
|
||||
$node->addElement($parser->parseExpression());
|
||||
}
|
||||
}
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed');
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\Unary\SpreadUnary;
|
||||
use Twig\Node\Expression\Unary\StringCastUnary;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
@@ -77,6 +78,13 @@ class ArrayExpression extends AbstractExpression implements SupportDefinedTestIn
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty expressions which are only allowed in destructuring
|
||||
foreach ($this->getKeyValuePairs() as $pair) {
|
||||
if ($pair['value'] instanceof EmptyExpression) {
|
||||
throw new SyntaxError('Empty array elements are only allowed in destructuring assignments.', $pair['value']->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
}
|
||||
|
||||
$compiler->raw('[');
|
||||
$isSequence = true;
|
||||
foreach ($this->getKeyValuePairs() as $i => $pair) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\EmptyExpression;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DestructuringSetBinary extends AbstractBinary
|
||||
{
|
||||
private array $variables = [];
|
||||
|
||||
/**
|
||||
* @param ArrayExpression $left The array expression containing variables to assign to
|
||||
* @param AbstractExpression $right The expression providing values for assignment
|
||||
*/
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
foreach ($left->getKeyValuePairs() as $pair) {
|
||||
if ($pair['value'] instanceof EmptyExpression) {
|
||||
$this->variables[] = null;
|
||||
} elseif ($pair['value'] instanceof ContextVariable) {
|
||||
$this->variables[] = $pair['value']->getAttribute('name');
|
||||
} else {
|
||||
throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned in destructuring.', $pair['value']::class), $lineno);
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($left, $right, $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
$compiler->raw('[');
|
||||
foreach ($this->variables as $i => $name) {
|
||||
if ($i) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
if (null !== $name) {
|
||||
$compiler->raw('$context[')->repr($name)->raw(']');
|
||||
}
|
||||
}
|
||||
$compiler->raw('] = array_pad(')->subcompile($this->getNode('right'))->raw(', ')->repr(\count($this->variables))->raw(', null)');
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('=');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents an empty slot in an array.
|
||||
*
|
||||
* This is currently only used in destructuring contexts.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EmptyExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(int $lineno)
|
||||
{
|
||||
parent::__construct([], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -231,6 +231,15 @@ class ExpressionParserTest extends TestCase
|
||||
$parser->parse($stream);
|
||||
}
|
||||
|
||||
public function testSequenceCompilationError()
|
||||
{
|
||||
$env = new Environment(new ArrayLoader(['index' => '{{ [1,,2] }}']), ['cache' => false, 'autoescape' => false]);
|
||||
|
||||
$this->expectException(SyntaxError::class);
|
||||
$this->expectExceptionMessage('Empty array elements are only allowed in destructuring assignments');
|
||||
$env->compileSource(new Source('{{ [1,,2] }}', 'index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTestsForString
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,12 @@ Twig supports the "=" operator (assignment)
|
||||
{# = can be chained #}
|
||||
{% do c = d = "a" %}{{ c }}{{ d }}
|
||||
{% do a = (b = 4) + 5 %}{{ a }}{{ b }}
|
||||
|
||||
# Array destructuring
|
||||
{% do [first, last] = ['Fabien', 'Potencier'] %}{{ first }} {{ last }}
|
||||
{% do [a, b] = [b, a] %}{{ a }}{{ b }}
|
||||
{% do [, second] = ['first', 'second'] %}{{ second }}
|
||||
{% do [x, y, z] = ['one', 'two'] %}{{ x }} {{ y }} {{ z is same as(null) ? 'null' : z }}
|
||||
--DATA--
|
||||
return []
|
||||
--EXPECT--
|
||||
@@ -18,3 +24,9 @@ return []
|
||||
4
|
||||
aa
|
||||
94
|
||||
|
||||
# Array destructuring
|
||||
Fabien Potencier
|
||||
49
|
||||
second
|
||||
one two null
|
||||
|
||||
Reference in New Issue
Block a user