mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-15 20:06:31 +00:00
Add support for object and mapping destructuring
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# 3.23.0 (2026-XX-XX)
|
# 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 `=` assignment operator (allows to set variables in expression or to replace the short-form of the set tag)
|
||||||
* Add sequence destructuring
|
* Add sequence, mapping, and object destructuring
|
||||||
* Add `?.` null-safe operator
|
* Add `?.` null-safe operator
|
||||||
* Add `===` and `!==` operators (equivalent to the `same as` and `not same as` tests)
|
* Add `===` and `!==` operators (equivalent to the `same as` and `not same as` tests)
|
||||||
|
|
||||||
|
|||||||
@@ -1147,6 +1147,25 @@ You can skip values by leaving a slot empty:
|
|||||||
{# only assign the second value #}
|
{# only assign the second value #}
|
||||||
{% do [, last] = ['Fabien', 'Potencier'] %}
|
{% do [, last] = ['Fabien', 'Potencier'] %}
|
||||||
|
|
||||||
|
Object Destructuring
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Use curly braces on the left side of an assignment to destructure an object
|
||||||
|
or mapping by extracting values based on property/key names:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% do {name, email} = user %}
|
||||||
|
|
||||||
|
{{ name }} {# user.name #}
|
||||||
|
{{ email }} {# user.email #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Object destructuring uses the :ref:`dot operator <dot_operator>` to access
|
||||||
|
values, so ``{name} = user`` is equivalent to ``name = user.name`` or
|
||||||
|
``name = user["name"]`` depending on the type of the variable.
|
||||||
|
|
||||||
.. _templates-whitespace-control:
|
.. _templates-whitespace-control:
|
||||||
|
|
||||||
Whitespace Control
|
Whitespace Control
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use Twig\ExpressionParser\InfixAssociativity;
|
|||||||
use Twig\Node\Expression\AbstractExpression;
|
use Twig\Node\Expression\AbstractExpression;
|
||||||
use Twig\Node\Expression\ArrayExpression;
|
use Twig\Node\Expression\ArrayExpression;
|
||||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||||
|
use Twig\Node\Expression\Binary\ObjectDestructuringSetBinary;
|
||||||
use Twig\Node\Expression\Binary\SequenceDestructuringSetBinary;
|
use Twig\Node\Expression\Binary\SequenceDestructuringSetBinary;
|
||||||
use Twig\Node\Expression\Binary\SetBinary;
|
use Twig\Node\Expression\Binary\SetBinary;
|
||||||
use Twig\Node\Expression\Variable\ContextVariable;
|
use Twig\Node\Expression\Variable\ContextVariable;
|
||||||
@@ -48,7 +49,11 @@ class AssignmentExpressionParser extends BinaryOperatorExpressionParser
|
|||||||
};
|
};
|
||||||
|
|
||||||
if ($left instanceof ArrayExpression) {
|
if ($left instanceof ArrayExpression) {
|
||||||
return new SequenceDestructuringSetBinary($left, $right, $token->getLine());
|
if ($left->isSequence()) {
|
||||||
|
return new SequenceDestructuringSetBinary($left, $right, $token->getLine());
|
||||||
|
} else {
|
||||||
|
return new ObjectDestructuringSetBinary($left, $right, $token->getLine());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return new SetBinary($left, $right, $token->getLine());
|
return new SetBinary($left, $right, $token->getLine());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,31 @@ class ArrayExpression extends AbstractExpression implements SupportDefinedTestIn
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the array is a sequence (keys are sequential integers starting from 0).
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function isSequence(): bool
|
||||||
|
{
|
||||||
|
foreach ($this->getKeyValuePairs() as $i => $pair) {
|
||||||
|
$key = $pair['key'];
|
||||||
|
if ($key instanceof TempNameExpression) {
|
||||||
|
$keyValue = $key->getAttribute('name');
|
||||||
|
} elseif ($key instanceof ConstantExpression) {
|
||||||
|
$keyValue = $key->getAttribute('value');
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($keyValue !== $i) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void
|
public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void
|
||||||
{
|
{
|
||||||
if (null === $key) {
|
if (null === $key) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?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\Variable\ContextVariable;
|
||||||
|
use Twig\Node\Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ObjectDestructuringSetBinary extends AbstractBinary
|
||||||
|
{
|
||||||
|
private array $properties = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ArrayExpression $left The array expression containing object/mapping destructuring properties
|
||||||
|
* @param AbstractExpression $right The expression providing values for assignment
|
||||||
|
*/
|
||||||
|
public function __construct(Node $left, Node $right, int $lineno)
|
||||||
|
{
|
||||||
|
if (!$left instanceof ArrayExpression) {
|
||||||
|
throw new \LogicException('Left side must be ArrayExpression for object/mapping destructuring.');
|
||||||
|
}
|
||||||
|
foreach ($left->getKeyValuePairs() as $pair) {
|
||||||
|
if (!$pair['value'] instanceof ContextVariable) {
|
||||||
|
throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned in object/mapping destructuring.', $pair['value']::class), $lineno);
|
||||||
|
}
|
||||||
|
$this->properties[] = $pair['value']->getAttribute('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($left, $right, $lineno);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler): void
|
||||||
|
{
|
||||||
|
$compiler->addDebugInfo($this);
|
||||||
|
$compiler->raw('[');
|
||||||
|
foreach ($this->properties as $i => $property) {
|
||||||
|
if ($i) {
|
||||||
|
$compiler->raw(', ');
|
||||||
|
}
|
||||||
|
$compiler->raw('$context[')->repr($property)->raw(']');
|
||||||
|
}
|
||||||
|
$compiler->raw('] = [');
|
||||||
|
foreach ($this->properties as $i => $property) {
|
||||||
|
if ($i) {
|
||||||
|
$compiler->raw(', ');
|
||||||
|
}
|
||||||
|
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ')->subcompile($this->getNode('right'))->raw(', ')->repr($property)->raw(', [], \\Twig\\Template::ANY_CALL, false, false, false, ')->repr($this->getNode('right')->getTemplateLine())->raw(')');
|
||||||
|
}
|
||||||
|
$compiler->raw(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function operator(Compiler $compiler): Compiler
|
||||||
|
{
|
||||||
|
return $compiler->raw('=');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,20 @@ Twig supports the "=" operator (assignment)
|
|||||||
{% do [a, b] = [b, a] %}{{ a }}{{ b }}
|
{% do [a, b] = [b, a] %}{{ a }}{{ b }}
|
||||||
{% do [, second] = ['first', 'second'] %}{{ second }}
|
{% do [, second] = ['first', 'second'] %}{{ second }}
|
||||||
{% do [x, y, z] = ['one', 'two'] %}{{ x }} {{ y }} {{ z is same as(null) ? 'null' : z }}
|
{% do [x, y, z] = ['one', 'two'] %}{{ x }} {{ y }} {{ z is same as(null) ? 'null' : z }}
|
||||||
|
|
||||||
|
# Object destructuring
|
||||||
|
{% do {name, email} = user %}{{ name }} {{ email }}
|
||||||
|
{% do {first_name, last_name} = user_map %}{{ first_name }} {{ last_name }}
|
||||||
|
{% do {name} = user_obj %}{{ name }}
|
||||||
--DATA--
|
--DATA--
|
||||||
return []
|
return [
|
||||||
|
'user' => (object)['name' => 'Fabien', 'email' => 'fabien@example.com'],
|
||||||
|
'user_map' => ['first_name' => 'Fabien', 'last_name' => 'Potencier'],
|
||||||
|
'user_obj' => new class {
|
||||||
|
public function getName() { return 'Fabien'; }
|
||||||
|
},
|
||||||
|
'null_obj' => null
|
||||||
|
]
|
||||||
--EXPECT--
|
--EXPECT--
|
||||||
4
|
4
|
||||||
44
|
44
|
||||||
@@ -30,3 +42,8 @@ Fabien Potencier
|
|||||||
49
|
49
|
||||||
second
|
second
|
||||||
one two null
|
one two null
|
||||||
|
|
||||||
|
# Object destructuring
|
||||||
|
Fabien fabien@example.com
|
||||||
|
Fabien Potencier
|
||||||
|
Fabien
|
||||||
|
|||||||
Reference in New Issue
Block a user