minor #4133 Simplify Loop code (fabpot)

This PR was squashed before being merged into the 4.x branch.

Discussion
----------

Simplify Loop code

Let's compute values as late as possible.

Commits
-------

437ad31d Move some code to LoopContext
0c2a69ce Simplify Loop code
This commit is contained in:
Fabien Potencier
2024-07-11 12:06:17 +02:00
6 changed files with 33 additions and 73 deletions
+2 -2
View File
@@ -44,11 +44,11 @@ class ForNode extends Node
->write("\$context['_parent'] = \$context;\n")
->write("\$$loopName = new \Twig\Runtime\Loop(")
->subcompile($this->getNode('seq'))
->raw(", \$context['_parent']);\n")
->raw(");\n")
;
if ($this->getAttribute('with_loop')) {
$compiler->write("\$context['loop'] = new \Twig\Runtime\LoopContext(\${$loopName});\n");
$compiler->write("\$context['loop'] = new \Twig\Runtime\LoopContext(\${$loopName}, \$context['_parent']);\n");
}
$compiler
+7 -51
View File
@@ -23,14 +23,10 @@ use Twig\Error\RuntimeError;
final class Loop implements \Iterator
{
private \Iterator $seq;
private bool $iterated;
private int $index0;
private int $revindex0;
private bool $first;
private bool $last;
private int $length;
public function __construct($seq, private $parent)
public function __construct($seq)
{
$this->seq = is_iterable($seq) ? (is_array($seq) ? new \ArrayIterator($seq) : $seq) : new \ArrayIterator([]);
$this->rewind();
@@ -49,29 +45,13 @@ final class Loop implements \Iterator
public function next(): void
{
$this->seq->next();
$this->iterated = true;
++$this->index0;
$this->first = false;
if (isset($this->length)) {
--$this->revindex0;
$this->last = 0 === $this->revindex0;
}
}
public function rewind(): void
{
$this->seq->rewind();
$this->iterated = false;
$this->index0 = 0;
$this->first = true;
if ($this->seq instanceof \Countable) {
$length = count($this->seq);
$this->revindex0 = $length - 1;
$this->length = $length;
$this->last = 1 === $length;
}
}
public function valid(): bool
@@ -81,21 +61,7 @@ final class Loop implements \Iterator
public function iterated(): bool
{
return $this->iterated;
}
public function getParent(): mixed
{
return $this->parent;
}
public function getRevindex0(): int
{
if (!$this->seq instanceof \Countable) {
throw new RuntimeError('The "loop.revindex0" variable is not defined as the loop iterates on a non-countable iterator.');
}
return $this->revindex0;
return 0 !== $this->index0;
}
public function getIndex0(): int
@@ -103,26 +69,16 @@ final class Loop implements \Iterator
return $this->index0;
}
public function getLength(): int
public function getLength($var = 'length'): int
{
if (!$this->seq instanceof \Countable) {
throw new RuntimeError('The "loop.length" variable is not defined as the loop iterates on a non-countable iterator.');
if (isset($this->length)) {
return $this->length;
}
return $this->length;
}
public function isFirst(): bool
{
return $this->first;
}
public function isLast(): bool
{
if (!$this->seq instanceof \Countable) {
throw new RuntimeError('The "loop.last" variable is not defined as the loop iterates on a non-countable iterator.');
throw new RuntimeError(sprintf('The "loop.%s" variable is not defined as the loop iterates on a non-countable iterator.', $var));
}
return $this->last;
return $this->length = count($this->seq);
}
}
+6 -6
View File
@@ -20,23 +20,23 @@ namespace Twig\Runtime;
*/
final class LoopContext
{
public function __construct(private Loop $loop)
public function __construct(private Loop $loop, private $parent)
{
}
public function getParent(): mixed
{
return $this->loop->getParent();
return $this->parent;
}
public function getRevindex0(): int
{
return $this->loop->getRevindex0();
return $this->loop->getLength('revindex0') - $this->getIndex();
}
public function getRevindex(): int
{
return $this->loop->getRevindex0() + 1;
return $this->loop->getLength('revindex') - $this->getIndex0();
}
public function getIndex0(): int
@@ -56,11 +56,11 @@ final class LoopContext
public function isFirst(): bool
{
return $this->loop->isFirst();
return 0 === $this->getIndex0();
}
public function isLast(): bool
{
return $this->loop->isLast();
return 0 === $this->loop->getLength('last') - $this->getIndex();
}
}
+9 -5
View File
@@ -4,16 +4,20 @@
{% for item in items %}
* {{ loop.index }}/{{ loop.index0 }}
* {{ loop.revindex }}/{{ loop.revindex0 }}
* {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
* {{ loop.first ? 'FIRST' : '-' }}/{{ loop.last ? 'LAST' : '-' }}/{{ loop.length }}
{% endfor %}
--DATA--
return ['items' => ['a', 'b']]
return ['items' => ['a', 'b', 'c']]
--EXPECT--
* 1/0
* 2/1
* 1//2
* 3/2
* FIRST/-/3
* 2/1
* 2/1
* -/-/3
* 3/2
* 1/0
* /1/2
* -/LAST/3
+2 -2
View File
@@ -2,7 +2,7 @@
"for" tag loop variable throws when last is used on a non-countable
--TEMPLATE--
{% for item in items %}
* {{ loop.last }}
* {{ loop.revindex }}
{% endfor %}
--DATA--
class ItemsIteratorNotCountable implements \Iterator
@@ -16,4 +16,4 @@ class ItemsIteratorNotCountable implements \Iterator
}
return ['items' => new ItemsIteratorNotCountable()]
--EXCEPTION--
Twig\Error\RuntimeError: The "loop.last" variable is not defined as the loop iterates on a non-countable iterator in "index.twig" at line 3.
Twig\Error\RuntimeError: The "loop.revindex" variable is not defined as the loop iterates on a non-countable iterator in "index.twig" at line 3.
+7 -7
View File
@@ -57,7 +57,7 @@ class ForTest extends NodeTestCase
$tests[] = [$node, <<<EOF
// line 1
\$context['_parent'] = \$context;
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('items')}, \$context['_parent']);
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('items')});
foreach (\$__internal_compile_0 as \$context["key"] => \$context["item"]) {
yield {$this->getVariableGetter('foo')};
}
@@ -78,8 +78,8 @@ EOF
$tests[] = [$node, <<<EOF
// line 1
\$context['_parent'] = \$context;
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('values')}, \$context['_parent']);
\$context['loop'] = new \Twig\Runtime\LoopContext(\$__internal_compile_0);
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('values')});
\$context['loop'] = new \Twig\Runtime\LoopContext(\$__internal_compile_0, \$context['_parent']);
foreach (\$__internal_compile_0 as \$context["k"] => \$context["v"]) {
yield {$this->getVariableGetter('foo')};
}
@@ -100,8 +100,8 @@ EOF
$tests[] = [$node, <<<EOF
// line 1
\$context['_parent'] = \$context;
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('values')}, \$context['_parent']);
\$context['loop'] = new \Twig\Runtime\LoopContext(\$__internal_compile_0);
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('values')});
\$context['loop'] = new \Twig\Runtime\LoopContext(\$__internal_compile_0, \$context['_parent']);
foreach (\$__internal_compile_0 as \$context["k"] => \$context["v"]) {
yield {$this->getVariableGetter('foo')};
}
@@ -122,8 +122,8 @@ EOF
$tests[] = [$node, <<<EOF
// line 1
\$context['_parent'] = \$context;
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('values')}, \$context['_parent']);
\$context['loop'] = new \Twig\Runtime\LoopContext(\$__internal_compile_0);
\$__internal_compile_0 = new \Twig\Runtime\Loop({$this->getVariableGetter('values')});
\$context['loop'] = new \Twig\Runtime\LoopContext(\$__internal_compile_0, \$context['_parent']);
foreach (\$__internal_compile_0 as \$context["k"] => \$context["v"]) {
yield {$this->getVariableGetter('foo')};
}