Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.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
在PHP7.4+;中,如何在foreach循环之前/之后以有效的方式触发代码(仅当输入此循环时);?_Php_Loops_Foreach_Iterable_Php 7.4 - Fatal编程技术网

在PHP7.4+;中,如何在foreach循环之前/之后以有效的方式触发代码(仅当输入此循环时);?

在PHP7.4+;中,如何在foreach循环之前/之后以有效的方式触发代码(仅当输入此循环时);?,php,loops,foreach,iterable,php-7.4,Php,Loops,Foreach,Iterable,Php 7.4,这有点类似于问题:,然而,+10年前的问题是严重的array导向的,并且没有一个答案与许多不同类型可以循环的事实相兼容 在PHP>=7.4(数组、迭代器、生成器、PDOStatement、DatePeriod、对象属性等)中可以迭代的对象上有一个循环,我们如何有效地触发需要在循环之前/之后发生的代码,,但只有在进入循环的情况下才能触发 典型的用例可能是生成HTML列表: <ul> <li>...</li> <li>...</li>

这有点类似于问题:,然而,+10年前的问题是严重的
array
导向的,并且没有一个答案与许多不同类型可以循环的事实相兼容

在PHP>=7.4(数组、迭代器、生成器、
PDOStatement
DatePeriod
、对象属性等)中可以迭代的对象上有一个循环,我们如何有效地触发需要在循环之前/之后发生的代码,,但只有在进入循环的情况下才能触发

典型的用例可能是生成HTML列表:

<ul>
  <li>...</li>
  <li>...</li>
  ...
</ul>
这样的脚本应产生以下输出:

<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<ul>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<ul>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
<ul>
<li>10</li>
<li>11</li>
<li>12</li>
</ul>
<ul>
<li>2020-01-02T00:00:00+00:00</li>
<li>2020-01-03T00:00:00+00:00</li>
<li>2020-01-04T00:00:00+00:00</li>
</ul>
  • 一,
  • 二,
  • 三,
  • 四,
  • 五,
  • 六,
  • 七,
  • 八,
  • 九,
  • 十,
  • 十一,
  • 十二,
  • 2020-01-02T00:00:00+00:00
  • 2020-01-03T00:00:00+00:00
  • 2020-01-04T00:00:00+00:00

这至少是一个有趣的思维实验

class BeforeAfterIterator implements Iterator
{
    private $iterator;

    public function __construct(iterable $iterator, $before, $after)
    {
        if (!$iterator instanceof Iterator) {
            $iterator = (function ($iterable) {
                yield from $iterable;
            })($iterator);
        }

        if ($iterator->valid()) {
            $this->iterator = new AppendIterator();
            $this->iterator->append(new ArrayIterator([$before]));
            $this->iterator->append($iterator);
            $this->iterator->append(new ArrayIterator([$after]));
        } else {
            $this->iterator = new ArrayIterator([]);
        }
    }

    public function current()
    {
        return $this->iterator->current();
    }

    public function next()
    {
        $this->iterator->next();
    }

    public function key()
    {
        return $this->iterator->key();
    }

    public function valid()
    {
        return $this->iterator->valid();
    }

    public function rewind()
    {
        $this->iterator->rewind();
    }
}
用法示例:

function generator() {
    foreach (range(1, 5) as $x) {
        yield "<li>$x";
    }
}

$beforeAfter = new \BeforeAfterIterator(generator(), '<ul>', '</ul>');

foreach ($beforeAfter as $value) {
    echo $value, PHP_EOL;
}
函数生成器(){
foreach(范围(1,5)为$x){
收益率“
  • $x”; } } $beforeAfter=new\BeforeAfterIterator(生成器(),“
      ”,“
    ”); foreach($beforefeater作为$value){ echo$value,PHP\u EOL; }
  • 输出

    <ul>
    <li>1
    <li>2
    <li>3
    <li>4
    <li>5
    </ul>
    
    • 一,
    • 二,
    • 三,
    • 四,
    • 五,
    如果您传递的迭代器不产生任何值,则不会得到任何输出


    我决不认为这是个好主意,这完全取决于你。我相信,对于一些比较晦涩的SPL类,有一些更优雅的方法可以做到这一点。通过扩展而不是合成,可能更简单。

    我认为我们不需要为语言添加任何新内容。在PHP7.1中,我们收到了一个新类型。使用类型暗示,可以强制函数/方法只接受可以迭代且语义上应该迭代的参数(与不实现可遍历的对象相反)

    正如您在这个模糊的示例中所看到的,您的代码中不可能有如此完美的一致性

    我们在PHP中使用不同的迭代方法的原因是,没有“一刀切”的解决方案。你想做的恰恰相反。您正在尝试创建一个解决方案,该解决方案适用于可以迭代的所有内容,即使它可能不应该被迭代。相反,您应该编写代码,它可以适当地处理所有方法

    function iterateOverIt(iterable $iterable) {
        if (is_array($iterable)) {
            echo 'The parameter is an array'.PHP_EOL;
            if ($iterable) {
                // foreach
            }
        } elseif ($iterable instanceof Iterator) {
            echo 'The parameter is a traversable object'.PHP_EOL;
            /**
             * @var \Iterator
             */
            $iterable = $iterable;
            if ($iterable->valid()) {
                // foreach
            }
        } elseif ($iterable instanceof Traversable) {
            echo 'The parameter is a traversable object'.PHP_EOL;
            // I donno, some hack?
            foreach ($iterable as $i) {
                // do PRE
                foreach ($iterable as $el) {
                    // foreach calls reset first on traversable objects. Each loop should start anew
                }
                // do POST
                break;
            }
        } else {
            // throw new Exception?
        }
    }
    

    如果你真的想要,你甚至可以使用
    is_object()
    在其中包含普通对象,但是正如我所说的,除非它实现了
    可遍历的
    ,否则不要对它进行迭代。

    一个适用于所有情况的解决方案(PHP>=7.2)是使用一个double,其中第一个像一个,不会真正执行循环,但它的始作俑者是:

    function iterateOverIt($iterable) {
        // First foreach acts like a guard condition
        foreach ($iterable as $_) {
    
            // Pre-processing:
            echo "<ul>\n";
    
            // Real looping, this won't start a *NEW* iteration process but will continue the one started above:
            foreach ($iterable as $item) {
                echo "<li>", $item instanceof DateTime ? $item->format("c") : (
                    isset($item["col"]) ? $item["col"] : $item
                ), "</li>\n";
            }
    
            // Post-processing:
            echo "</ul>\n";
            break;
        }
    }
    
    函数iterateOverIt($iterable){
    //第一个foreach就像一个守卫条件
    foreach($iterable as$){
    //预处理:
    回声“
      \n”; //真正的循环,这不会启动*新*迭代过程,但会继续上面启动的过程: foreach($iterable作为$item){ echo“
    • ”,$item instanceof DateTime?$item->format(“c”):( isset($item[“col”])?$item[“col”]:$item ),“
    • \n”; } //后处理: 回声“
    \n”; 打破 } }

    .

    您可以使用输出缓冲功能。缓冲循环的输出。然后检查缓冲区是否为空。或者只是将循环结果放在字符串中,而不是回显它,然后检查字符串是否为空。对我来说,这有点过分,因为它会导致中间字符串增长,无论是PHP变量,还是PHP的内部输出缓冲。然后,对布尔值重复测试似乎要轻得多。除此之外,这里的打印只是一个示例,我正在寻找一种在foreach循环中进行预处理和后处理的通用方法。循环可能不会打印任何内容。我甚至想不出如何将其添加到语言中。生成器通常无法知道它在最后一次迭代中的时间。我相信这项任务是不可能的,因为在您对它进行迭代之前,无法判断生成器是否真的会生成任何值。有趣的想法是,这实际上只能传递实现
    迭代器的内容。这个问题实际上是关于
    foreach
    以及
    foreach
    可以迭代的所有可能类型的。@patrickallert这是一个非常公平的观点。我已将其更新为支持的所有实例(包括阵列),方法是将它们转换为生成器。有关另一个示例,请参见编辑后需要删除类型提示。它不再接受类型为IteratorDatePeriod的对象mysqli_的结果仍然不起作用。这个问题似乎太理论化了。这个问题要求的是一个设计过度、过于笼统的答案。实际上,我的代码有一个
    iterable
    类型提示。我只是想把关于
    foreach
    的问题概括一下。是的,存在一个通用的解决方案(它也适用于任何可以迭代的东西,包括对象),即使这不是我的目标。我想这是一个通用的解决方案。这就是我在回答中建议的,对于您的简单场景,它应该会起作用。我可能应该修改我的答案。
    class SideEffects implements Iterator {
        private $position = 0;
    
        private $IHoldValues = [1, 1, 2, 3, 5, 8];
    
        public function __construct() {
            $this->position = 0;
        }
    
        public function doMeBefore() {
            $this->IHoldValues = [];
            echo "HAHA!".PHP_EOL;
        }
    
        public function rewind() {
            $this->position = 0;
        }
    
        public function current() {
            return $this->IHoldValues[$this->position];
        }
    
        public function key() {
            return $this->position;
        }
    
        public function next() {
            ++$this->position;
        }
    
        public function valid() {
            return isset($this->IHoldValues[$this->position]);
        }
    }
    
    $it = new SideEffects();
    if (1/* Some logic to check if the iterator is valid */) {
        $it->doMeBefore(); // causes side effect, which invalidates previous statement
        foreach ($it as $row) {
            echo $row.PHP_EOL;
        }
    }
    
    function iterateOverIt(iterable $iterable) {
        if (is_array($iterable)) {
            echo 'The parameter is an array'.PHP_EOL;
            if ($iterable) {
                // foreach
            }
        } elseif ($iterable instanceof Iterator) {
            echo 'The parameter is a traversable object'.PHP_EOL;
            /**
             * @var \Iterator
             */
            $iterable = $iterable;
            if ($iterable->valid()) {
                // foreach
            }
        } elseif ($iterable instanceof Traversable) {
            echo 'The parameter is a traversable object'.PHP_EOL;
            // I donno, some hack?
            foreach ($iterable as $i) {
                // do PRE
                foreach ($iterable as $el) {
                    // foreach calls reset first on traversable objects. Each loop should start anew
                }
                // do POST
                break;
            }
        } else {
            // throw new Exception?
        }
    }
    
    function iterateOverIt($iterable) {
        // First foreach acts like a guard condition
        foreach ($iterable as $_) {
    
            // Pre-processing:
            echo "<ul>\n";
    
            // Real looping, this won't start a *NEW* iteration process but will continue the one started above:
            foreach ($iterable as $item) {
                echo "<li>", $item instanceof DateTime ? $item->format("c") : (
                    isset($item["col"]) ? $item["col"] : $item
                ), "</li>\n";
            }
    
            // Post-processing:
            echo "</ul>\n";
            break;
        }
    }