我使用的是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
关键字如何处理迭代程序?
我问的原因是,我正在编写的代码可能被赋予ArrayIterator
或MongoCursor
。两者都实现了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
}