NoRewindIterator什么时候倒回内部迭代器?


When does the NoRewindIterator rewind the inner Iterator?

当PHP的NoRewindIterator倒带时,它的名字是非常清楚的:never.

但有时候,尤其是当我从它延伸出来的时候,我问自己:它会倒回去吗?它会倒带一次吗?因此,如果我需要它,我是否需要实现(第一次)倒带一些 Traversable 可能需要它?

我特别想知道这里的内部迭代器,所以NoRewindIterator类将Iterator作为其构造函数的参数。

例如:

$iterator = new SomeIterator(); // implements Iterator
$noRewind = new NoRewindIterator($iterator);
foreach ($noRewind as $value) {
    break;
}

当使用$noRewind时,$iterator->rewind()将永远不会被调用吗?或者它会在第一次调用$noRewind->rewind()时被调用一次(例如在示例中的第一个foreach中)。或者在构造上?

就像单独的类名(NoRewindIterator)一样,手册有以下具体的措辞:

NoRewindIterator -该迭代器不能重绕。

具体方法:

NoRewindIterator::rewind() -禁止对内部迭代器进行倒带操作。

这意味着没有将Iterator::rewind()方法传递给内部迭代器。测试也显示了这一点,下面是我一直在运行的一个简单的测试(所有不属于PHP的迭代器的代码都在迭代器花园中):

$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";
}

在这段代码中,调试迭代器动态打印(回显)迭代信息:

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

如上图所示,$iterator->rewind()从未被调用。

这也是有意义的,原因与另一个相关问题相同:为什么我必须倒带IteratorIteratorNoRewindIteratorIteratorIterator扩展到它的父类,getInnerIterator()方法返回Iterator而不是Traversable

这个改变允许你在需要的时候初始化倒带:

echo "'n'$calling noRewind->getInnerIterator()->rewind():'n";
$noRewind->getInnerIterator()->rewind();
echo "'nsecond foreach:'n";
foreach ($noRewind as $value) {
    echo "iteration value: $value'n";
}

示例调试输出:

$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

了解这些细节,然后允许创建OneTimeRewindIterator,例如:

/**
 * Class OneTimeRewindIterator
 */
class OneTimeRewindIterator extends NoRewindIterator
{
    private $didRewind = FALSE;
    public function rewind() {
        if ($this->didRewind) return;
        $this->didRewind = TRUE;
        $this->getInnerIterator()->rewind();
    }
}

NoRewindIterator在某些情况下可以倒带。

。当你使用continue到一个循环外:

<?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;
    }
}

可以期望它输出1 = 1,2 × 2,等等,但实际上这段代码输出的是:

1   1
2   1
3   1
4   1

continue 2与普通break交换不会改变任何东西。

<?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;
    }
}

如果你用调试迭代器包装这个迭代器,你会看到next()永远不会被调用,即使你以任何方式打破循环。因此,您将始终接收到第一个值。然而,迭代器似乎被倒带了。