mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-01 04:57:23 +00:00
Fix Traversable sequence destructuring semantics
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# 3.29.0 (2026-XX-XX)
|
||||
|
||||
* Add documentation comments to attach metadata to nodes (experimental)
|
||||
* Fix sequence destructuring of iterators throwing a `TypeError`
|
||||
* Add `TempestMarkdown` to use `tempest/markdown` as the `markdown_to_html` converter
|
||||
* Fix imported macros not resolving their own template-level macro imports
|
||||
* Add the `include_only` function to render a template without giving it access to the current context
|
||||
|
||||
@@ -1210,6 +1210,15 @@ You can skip values by leaving a slot empty:
|
||||
{# only assign the second value #}
|
||||
{% do [, last] = ['Fabien', 'Potencier'] %}
|
||||
|
||||
.. versionadded:: 3.29
|
||||
|
||||
Support for destructuring iterators was introduced in Twig 3.29.
|
||||
|
||||
Sequence destructuring also works with iterators (any ``Traversable``
|
||||
value). Values are extracted in iteration order and keys are ignored. The
|
||||
iterator is consumed lazily: only as many values as there are variables are
|
||||
fetched, and the expression returns the iterator itself.
|
||||
|
||||
Object Destructuring
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -1401,6 +1401,39 @@ final class CoreExtension extends AbstractExtension
|
||||
return $preserveKeys ? $seq : array_values($seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string|null> $names
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function destructureSequence(array &$context, array $names, \Traversable $sequence): \Traversable
|
||||
{
|
||||
$count = \count($names);
|
||||
if (0 === $count) {
|
||||
return $sequence;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($sequence as $value) {
|
||||
$name = $names[$i];
|
||||
if (null !== $name) {
|
||||
$context[$name] = $value;
|
||||
}
|
||||
if (++$i === $count) {
|
||||
return $sequence;
|
||||
}
|
||||
}
|
||||
|
||||
for (; $i < $count; ++$i) {
|
||||
$name = $names[$i];
|
||||
if (null !== $name) {
|
||||
$context[$name] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a variable is empty.
|
||||
*
|
||||
|
||||
@@ -48,7 +48,14 @@ class SequenceDestructuringSetBinary extends AbstractBinary
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
$compiler->raw('[');
|
||||
$var = '$'.$compiler->getVarName();
|
||||
$compiler
|
||||
->raw('(('.$var.' = ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(') instanceof \Traversable ? CoreExtension::destructureSequence($context, ')
|
||||
->repr($this->variables)
|
||||
->raw(', '.$var.') : ([')
|
||||
;
|
||||
foreach ($this->variables as $i => $name) {
|
||||
if ($i) {
|
||||
$compiler->raw(', ');
|
||||
@@ -57,15 +64,11 @@ class SequenceDestructuringSetBinary extends AbstractBinary
|
||||
$compiler->raw('$context[')->repr($name)->raw(']');
|
||||
}
|
||||
}
|
||||
$compiler->raw('] = array_pad((');
|
||||
$var = '$'.$compiler->getVarName();
|
||||
$compiler
|
||||
->raw($var.' = ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(') instanceof \Traversable ? iterator_to_array('.$var.') : '.$var)
|
||||
->raw(', ')
|
||||
->raw('] = array_pad('.$var.', ')
|
||||
->repr(\count($this->variables))
|
||||
->raw(', null)');
|
||||
->raw(', null)))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
|
||||
@@ -23,7 +23,8 @@ Twig supports the "=" operator (assignment)
|
||||
{% do {name} = user_obj %}{{ name }}
|
||||
|
||||
# Array destructuring from a Traversable
|
||||
{% do [p, q] = pair_traversable %}{{ p }} {{ q }}
|
||||
{% do destructured = ([p, q] = pair_traversable) %}{{ p }} {{ q }} {{ pair_traversable.yielded }} {{ destructured is same as(pair_traversable) ? 'same' : 'different' }}
|
||||
{% do [r, , t] = short_traversable %}{{ r }} {{ t is same as(null) ? 'null' : t }}
|
||||
|
||||
# Object destructuring with renaming
|
||||
{% do {name: userName, email: userEmail} = user %}{{ userName }} {{ userEmail }}
|
||||
@@ -38,7 +39,18 @@ return [
|
||||
public function getName() { return 'Fabien'; }
|
||||
},
|
||||
'null_obj' => null,
|
||||
'pair_traversable' => new ArrayIterator(['Fabien', 'Potencier']),
|
||||
'pair_traversable' => new class implements IteratorAggregate {
|
||||
public int $yielded = 0;
|
||||
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
foreach ([5 => 'Fabien', 8 => 'Potencier', 13 => 'Ignored'] as $key => $value) {
|
||||
++$this->yielded;
|
||||
yield $key => $value;
|
||||
}
|
||||
}
|
||||
},
|
||||
'short_traversable' => new ArrayIterator(['one']),
|
||||
]
|
||||
--EXPECT--
|
||||
4
|
||||
@@ -59,7 +71,8 @@ Fabien Potencier
|
||||
Fabien
|
||||
|
||||
# Array destructuring from a Traversable
|
||||
Fabien Potencier
|
||||
Fabien Potencier 2 same
|
||||
one null
|
||||
|
||||
# Object destructuring with renaming
|
||||
Fabien fabien@example.com
|
||||
|
||||
Reference in New Issue
Block a user