我用普通数组和 PHP 迭代器都试过了。不幸的是,PHP 迭代器,因为它们是对象,工作方式不同。对象是按引用传递的,而数组是按值传递的。因此,当嵌套的 foreach 到达迭代器的末尾时,第一个 foreach 无法从它停止的地方继续,因为内部指针设置为最后一个元素。
考虑以下使用普通 PHP 数组编写的示例:
$test = [1, 2, 3];
foreach ($test as $i1 => $v1) {
echo "first loop: $i1\n";
foreach ($test as $i2 => $v2) {
echo "second loop: $i2\n";
}
}
上面的 sn-p 产生以下输出:
first loop: 0
second loop: 0
second loop: 1
second loop: 2
first loop: 1
second loop: 0
second loop: 1
second loop: 2
first loop: 2
second loop: 0
second loop: 1
second loop: 2
如果我们用迭代器尝试同样的事情,我们会得到完全不同的结果。为避免混淆,我将使用 ArrayIterator 类,以便 PHP 人员已经实现了所有内容,并且我们最终不会以错误的方式使用接口。所以这里没有错误的余地,这就是他们实现迭代器的方式:
$test = new ArrayIterator([1, 2, 3]);
foreach ($test as $i1 => $v1) {
echo "first loop: $i1\n";
foreach ($test as $i2 => $v2) {
echo "second loop: $i2\n";
}
}
输出是:
first loop: 0
second loop: 0
second loop: 1
second loop: 2
如您所见,第一个 foreach 只执行一次。
一种解决方法可能是实现SeekableIterator 接口。它可以让我们使用 seek() 方法将内部指针重置为正确的值。在我看来,这是一个不好的做法,但如果 PHP 人员不解决这个问题,我真的不能说它可能是最好的。从现在开始,我可能会避免使用迭代器,因为它们的行为似乎与我认为人们最初假设的数组不同。因此,使用它们会使我的应用程序容易出错,因为我团队中的开发人员可能不知道这一点并弄乱了代码。
以 SeekableIterator 接口为例:
class MyIterator implements SeekableIterator
{
private $position = 0;
private $array = [1, 2, 3];
public function __construct()
{
$this->position = 0;
}
public function rewind()
{
$this->position = 0;
}
public function current()
{
return $this->array[$this->position];
}
public function key()
{
return $this->position;
}
public function next()
{
++$this->position;
}
public function valid()
{
return isset($this->array[$this->position]);
}
public function seek($position)
{
$this->position = $position;
}
}
$test = new MyIterator();
foreach ($test as $i1 => $v1) {
echo "first loop $i1\n";
foreach ($test as $i2 => $v2) {
echo "second loop $i2\n";
}
$test->seek($i1);
}
输出如任何人所料:
first loop: 0
second loop: 0
second loop: 1
second loop: 2
first loop: 1
second loop: 0
second loop: 1
second loop: 2
first loop: 2
second loop: 0
second loop: 1
second loop: 2
所有这些都是因为每个 foreach 都在其自己的数组副本上工作。迭代器,因为它们是对象,所以通过引用传递。因此,每个 foreach 共享相同的对象。如果您尝试取消设置嵌套 foreach 中的元素,也会发生同样的事情。未设置将增加内部指针。然后执行到达嵌套 foreach 的末尾,内部指针再次增加。这意味着在未设置的情况下,我们将内部指针增加了两倍。因此,父 foreach 将跳过一个元素。
我的建议是,如果您无法避免迭代器,请务必非常小心。始终对它们进行彻底的单元测试。
注意:代码在 PHP 5.6.14 和 PHP 7.0.0 RC5 上测试。