Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/234.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 带迭代器接口的嵌套foreach_Php_Iterator_Foreach - Fatal编程技术网

Php 带迭代器接口的嵌套foreach

Php 带迭代器接口的嵌套foreach,php,iterator,foreach,Php,Iterator,Foreach,$this->criteria和$criteria是实现php迭代器接口的同一个对象。有没有一种简单的方法可以让这段代码正常工作,或者php迭代器接口不支持嵌套的foreach循环?那么,第二个foreach将在运行之前调用$iterator->reset()。因此,当第二个foreach到达迭代器的末尾时,内部指针已经位于数组的末尾 就像: foreach ($criteria as $key => $value) { $params[$key] = $value; } 购买它

$this->criteria和$criteria是实现php迭代器接口的同一个对象。有没有一种简单的方法可以让这段代码正常工作,或者php迭代器接口不支持嵌套的foreach循环?

那么,第二个foreach将在运行之前调用
$iterator->reset()
。因此,当第二个foreach到达迭代器的末尾时,内部指针已经位于数组的末尾

就像:

foreach ($criteria as $key => $value) {
    $params[$key] = $value;
}
购买它到达外部循环中的
$it->next()
调用的时间,它已经无效。因此
next()
调用将“失败”,而
$it->valid()
将返回false

这不是迭代器的问题,而是您使用的逻辑的问题。如果确实必须嵌套循环,则
克隆
内部循环中的迭代器(
$subit=clone$it
),这样就不会干扰指针

编辑:克隆示例:

$it->reset();
while ($it->valid()) {
   $it->reset();
   while ($it->valid()) {
       //do something
       $it->next();
   }
   $it->next();
}
或者,使用foreach(语义等价):


我用普通数组和PHP迭代器都试过了。不幸的是,PHP迭代器,因为它们是对象,工作方式不同。对象是通过引用传递的,而数组是通过值传递的。因此,当嵌套的foreach到达迭代器的末尾时,第一个foreach无法继续它停止的位置,因为内部指针被设置为最后一个元素

考虑以下使用普通PHP数组编写的示例:

$test=[1,2,3];
foreach($i1=>v1测试){
echo“第一个循环:$i1\n”;
foreach($i2=>$v2的测试){
echo“第二个循环:$i2\n”;
}
}
上述代码段生成以下输出:

foreach ($it as $key => $value) {
    $subit = clone $it;
    foreach ($subit as $k => $v) {
        //Do stuff
    }
}
如果我们用迭代器尝试同样的事情,我们会得到完全不同的结果。为了避免混淆,我将使用该类,以便PHP人员已经实现了所有内容,并且我们不会以错误的方式使用接口。这里没有错误的空间,迭代器就是这样实现的:

$test=新的阵列迭代器([1,2,3]);
foreach($i1=>v1测试){
echo“第一个循环:$i1\n”;
foreach($i2=>$v2的测试){
echo“第二个循环:$i2\n”;
}
}
输出为:

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只执行一次

解决方法可以是实现该接口。它允许我们使用seek()方法将内部指针重置为正确的值。在我看来,这是一个糟糕的做法,但如果PHP的家伙不解决这个问题,我真的不能说什么是最好的。从现在起,我可能会避免使用迭代器,因为迭代器的行为似乎与数组不同,我认为这是人们一开始的假设。因此,使用它们会使我的应用程序容易出错,因为我的团队中的一个开发人员可能不知道这一点,并且会弄乱代码

下面是一个带有SeekableTerator界面的示例:

first loop: 0
second loop: 0
second loop: 1
second loop: 2
输出正如任何人所期望的:

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);
}
所有这些都是因为每个foreach都处理自己的数组副本。迭代器是对象,因此通过引用传递。因此,每个foreach共享同一个对象。如果尝试取消设置嵌套foreach中的元素,也会发生同样的情况。unset将增加内部指针。然后执行到达嵌套foreach的末尾,内部指针再次增加。这意味着,对于unset,我们将内部指针增加两倍。因此,父foreach将跳过一个元素

我的建议是,如果你不能避免迭代器,那就要非常小心。始终对它们进行彻底的单元测试

注意:在PHP 5.6.14和PHP 7.0.0 RC5上测试的代码。

编辑: 发布这篇文章后,我意识到如果在嵌套的foreach中执行
continue
break
,这篇文章将严重中断。因此,这可能不是您想要的解决方案。

如其他答案所述,PHP foreach调用
foreach
循环开始时倒带
,在每次迭代结束时倒带
valid
。因此,在嵌套的
foreach
中,迭代器无效,并以这种方式保留在父
foreach
中。这里是一个黑客的解决方法,它使用指针堆栈而不是单个指针,并使迭代器在本例中的行为类似于数组

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
输出:

class Test implements Iterator {
    private $loopstack = [];

    private $array = array("A", "B", "C",);

    function rewind() {
        $this->loopstack[] = 0;
    }

    function current() {
        return $this->array[end($this->loopstack)];
    }

    function key() {
        return end($this->loopstack);
    }

    function next() {
        array_push($this->loopstack, array_pop($this->loopstack) + 1);
    }

    function valid() {
        $valid = isset($this->array[end($this->loopstack)]);
        if (!$valid) {
            array_pop($this->loopstack);
        }
        return $valid;
    }
}

$iterator = new Test();
foreach ($iterator as $e){
    var_dump('loop1 ' . $e);
    foreach ($iterator as $e2){
        var_dump('loop2 ' . $e2);
    }
}

另一种选择是实施。如果碰巧实现迭代器的对象封装了大量数据,那么克隆可能会占用大量内存。@事实上,您应该根据您的注释编写一个答案。
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
class Test implements Iterator {
    private $loopstack = [];

    private $array = array("A", "B", "C",);

    function rewind() {
        $this->loopstack[] = 0;
    }

    function current() {
        return $this->array[end($this->loopstack)];
    }

    function key() {
        return end($this->loopstack);
    }

    function next() {
        array_push($this->loopstack, array_pop($this->loopstack) + 1);
    }

    function valid() {
        $valid = isset($this->array[end($this->loopstack)]);
        if (!$valid) {
            array_pop($this->loopstack);
        }
        return $valid;
    }
}

$iterator = new Test();
foreach ($iterator as $e){
    var_dump('loop1 ' . $e);
    foreach ($iterator as $e2){
        var_dump('loop2 ' . $e2);
    }
}
string(7) "loop1 A"
string(7) "loop2 A"
string(7) "loop2 B"
string(7) "loop2 C"
string(7) "loop1 B"
string(7) "loop2 A"
string(7) "loop2 B"
string(7) "loop2 C"
string(7) "loop1 C"
string(7) "loop2 A"
string(7) "loop2 B"
string(7) "loop2 C"