PHP应该如何';s迭代方法valid()、current()和next()的行为


How should PHP's Iterator methods valid(), current(), and next() behave?

我使用的是MAMP中的PHP 5.3.6。

我有一个用例,其中最好使用PHP的Iterator接口方法next()current()valid()来遍历集合。foreach循环在我的特定情况下对我不起作用。一个简化的while循环可能看起来像

<?php
while ($iter->valid()) {
  // do something with $iter->current()
  $iter->next();
}

当$iter实现PHP的Iterator接口时,上面的代码应该一直工作吗?PHP的foreach关键字如何处理迭代程序?

我问的原因是,我正在编写的代码可能被赋予ArrayIteratorMongoCursor。两者都实现了PHP的Iterator接口,但行为不同。我想知道PHP或PHP的Mongo扩展中是否有漏洞。

ArrayIterator::valid()在对next()的任何调用之前返回true--在创建ArrayIterator之后立即返回。

MongoCursor::valid()仅在对next()的第一次调用之后返回true。因此,上面的while循环永远不会执行。

冒着冗长的风险,下面的代码演示了这些断言:

<?php
// Set up array iterator
$arr = array("first");
$iter = new 'ArrayIterator($arr);
// Test array iterator
echo(($iter->valid() ? "true" : "false")."'n"); // Echoes true
var_dump($iter->current()."'n");                // "first"
$iter->next();
echo(($iter->valid() ? "true" : "false")."'n"); // Echoes false

// Set up mongo iterator
$m = new 'Mongo();
$collection = $m->selectDB("iterTest")->selectCollection("mystuff");
$collection->drop(); // Ensure collection is empty
$collection->insert(array('a' => 'b'));
$miter = $collection->find(); // should find one object
// Test mongo iterator
echo(($miter->valid() ? "true" : "false")."'n"); // Echoes false
$miter->next();
echo(($miter->valid() ? "true" : "false")."'n"); // Echoes true
var_dump($miter->current());                     // Array(...)

哪个实施是正确的?我发现很少有文档支持这两种行为,而且官方的PHP文档要么模棱两可,要么我读错了。Iterator::valid()的文档说明:

此方法在Iterator::rewind()和Iterator::next()之后调用,以检查当前位置是否有效。

这将建议我的while循环应该首先调用next()

然而Iterator::next的PHP文档指出:

此方法在每个foreach循环之后调用。

这表明我的while循环是正确的。

总而言之,PHP迭代器应该如何运行?

这是一个有趣的问题。我不知道为什么foreach不适合你,但我有一些想法。

看看Iterator接口参考页上给出的例子。它显示了PHP的内部foreach实现调用Iterator方法的顺序。特别是,请注意,当首次建立foreach时,第一个调用就是对rewind()的调用。这个例子虽然没有很好的注释,但却是我答案的基础。

我不知道为什么在调用next()之后的之前,MongoCursor不会为valid()返回true,但您应该能够通过在循环之前调用rewind()来重置任何类型的对象。所以你会有:

// $iter may be either MongoCursor or ArrayIterator
$iter->rewind();
while( $iter->valid() ){
    // do something with $iter->current()
    $iter->next();
}

我相信这应该对你有用。如果没有,Mongo类可能有一个错误。

编辑:Mike Purcell的回答正确地指出了ArrayTerator和Iterator不一样。然而,ArrayIdeator实现了Iterator,所以您应该能够使用rewind(),正如我在上面对它们中的任何一个所展示的那样。

对任何迭代器进行子类化,并在调用时进行echo'ing,这将告诉您它的行为。

示例(演示)

class MyArrayIterator extends ArrayIterator
{
    public function __construct ($array)
    {
        echo __METHOD__, PHP_EOL;
        parent::__construct($array);
    }
    …
}
foreach (new MyArrayIterator(range(1,3)) as $k => $v) {
    echo "$k => $v", PHP_EOL;
}

输出

MyArrayIterator::__construct
MyArrayIterator::rewind
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
0 => 1
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
1 => 2
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
2 => 3
MyArrayIterator::next
MyArrayIterator::valid

这相当于做

$iterator = new MyArrayIterator(range(1,3));
for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
    echo "{$iterator->key()} => {$iterator->current()}", PHP_EOL;
}

调用方法的顺序与自定义Iterator:相同

class MyIterator implements Iterator
{
    protected $iterations = 0;
    public function current()
    {
        echo __METHOD__, PHP_EOL;
        return $this->iterations;
    }
    public function key ()
    {
        echo __METHOD__, PHP_EOL;
        return $this->iterations;
    }
    public function next ()
    {
        echo __METHOD__, PHP_EOL;
        return $this->iterations++;
    }
    public function rewind ()
    {
        echo __METHOD__, PHP_EOL;
        return $this->iterations = 0;
    }
    public function valid ()
    {
        echo __METHOD__, PHP_EOL;
        return $this->iterations < 3;
    }
}
foreach (new MyIterator as $k => $v) {
    echo "$k => $v", PHP_EOL;
}

您可能混淆了Iterator和ArrayTerator,因为它们都有自己的valid() api调用。

  • 数组迭代器:没有具体提到next()等,但这个例子清楚地展示了你在OP中提到的内容;不需要进行其他AI api调用来确定数组的当前元素是否有效。

  • Iterator:正如您所提到的:"这个方法在Iterator::reward()和Iterator::next()之后调用,以检查当前位置是否有效。"

因此,我将坚持使用ArrayTerator,因为它展示了最正确的行为,因为valid()将正确地确定数组中的当前元素是否有效,而不必进行另一个api调用(下一个,倒带)。

如果你想让Mongo表现得像AI一样,你可以在启动while循环之前添加一个实例检查:

if ($iter instanceof MongoCursor) {
   $iter->next()
}
while ($iter->valid()) {
    // Do stuff
}