Reputation: 198119
By its name it is perfectly clear when PHP's NoRewindIterator
rewinds: never.
But sometimes, esp. when extending from it, I ask myself: Does it ever rewind? Does it rewind once? So if I need it, do I need to implement the (first) rewind as some Traversable
might require it?
I specifically wonder about the inner iterator here, so the Iterator
the NoRewindIterator
class takes as parameter with its constructor.
For example:
$iterator = new SomeIterator(); // implements Iterator
$noRewind = new NoRewindIterator($iterator);
foreach ($noRewind as $value) {
break;
}
Will $iterator->rewind()
be never called when using $noRewind
from here on? Or will it be called once for the first time $noRewind->rewind()
is invoked (e.g. within the first foreach
in the example). Or maybe on construct?
Upvotes: 1
Views: 759
Reputation: 30911
NoRewindIterator
can sort of rewind in some cases.
E.g. when you use continue to a cycle outside:
<?php
$iterator = new NoRewindIterator(new ArrayIterator([1, 2, 3, 4]));
foreach ([1, 2, 3, 4] as $a) {
foreach ($iterator as $b) {
echo "$a\t$b\n";
continue 2;
}
}
One could expect it to print ones be ones, twos by twos, and so on, but actually this code prints:
1 1
2 1
3 1
4 1
Swapping continue 2
with a plain break
does not change a thing.
<?php
$iterator = new \NoRewindIterator(new \ArrayIterator([1, 2, 3, 4]));
$array = [1, 2, 3, 4];
foreach ($array as $a) {
foreach ($iterator as $b) {
echo "$a\t$b\n";
break;
}
}
If you wrap this iterator with a debugging iterator you would see that next()
never gets called if you break the loop by any means. Therefore you would always receive the first values. Yet it would seem like the iterator got rewinded somehow.
Upvotes: 0
Reputation: 198119
Like by the name of the class alone (NoRewindIterator
), the manual has the following wording in specific:
NoRewindIterator - This iterator cannot be rewound.
And for the concrete method:
NoRewindIterator::rewind() - Prevents the rewind operation on the inner iterator.
This implies that the Iterator::rewind()
method is not passed through to the inner iterator. Tests also show this, here is a simple one I've been running (code of all iterators not part of PHP are in the Iterator Garden):
$iterator = new RangeIterator(1, 1);
$debug = new DebugIteratorDecorator($iterator);
$noRewind = new NoRewindIterator($debug);
echo "first foreach:\n";
foreach ($noRewind as $value) {
echo "iteration value: $value\n";
}
In this code, the debug-iterator prints (echoes) iteration information on the fly:
first foreach:
Iterating (RangeIterator): #0 valid()
Iterating (RangeIterator): #0 parent::valid() is TRUE
Iterating (RangeIterator): #0 current()
Iterating (RangeIterator): #0 parent::current() is 1
iteration value: 1
Iterating (RangeIterator): #1 next()
Iterating (RangeIterator): #1 after parent::next()
Iterating (RangeIterator): #1 valid()
Iterating (RangeIterator): #1 parent::valid() is FALSE
As this shows, $iterator->rewind()
is never called.
This also makes sense for the same reasons given in a related question: Why must I rewind IteratorIterator. The NoRewindIterator
extends from IteratorIterator
and different to it's parent class, the getInnerIterator()
method returns an Iterator
and not a Traversable
.
This change allows you to initialize the rewind when you need to:
echo "\n\$calling noRewind->getInnerIterator()->rewind():\n";
$noRewind->getInnerIterator()->rewind();
echo "\nsecond foreach:\n";
foreach ($noRewind as $value) {
echo "iteration value: $value\n";
}
Exemplary debug output again:
$calling noRewind->getInnerIterator()->rewind():
Iterating (RangeIterator): #0 rewind()
Iterating (RangeIterator): #0 after parent::rewind()
second foreach:
Iterating (RangeIterator): #0 valid()
Iterating (RangeIterator): #0 parent::valid() is TRUE
Iterating (RangeIterator): #0 current()
Iterating (RangeIterator): #0 parent::current() is 1
iteration value: 1
Iterating (RangeIterator): #1 next()
Iterating (RangeIterator): #1 after parent::next()
Iterating (RangeIterator): #1 valid()
Iterating (RangeIterator): #1 parent::valid() is FALSE
Knowing about these details then does allow to create a OneTimeRewindIterator
for example:
/**
* Class OneTimeRewindIterator
*/
class OneTimeRewindIterator extends NoRewindIterator
{
private $didRewind = FALSE;
public function rewind() {
if ($this->didRewind) return;
$this->didRewind = TRUE;
$this->getInnerIterator()->rewind();
}
}
Upvotes: 1