如何在 foreach 循环之前/之后触发代码,只有在 PHP 7.4+ 中以一种有效的方式输入这

这有点类似于问题:如何确定 foreach 循环中的第一次和最后一次迭代?,但是,这个 +10 岁的问题非常有array针对性,并且没有一个答案与可以循环许多不同类型的事实兼容。

给定一个可以在 PHP >= 7.4中迭代PDOStatement的东西(数组、迭代器、生成器、DatePeriod...、对象属性等)的循环,我们如何以有效的方式触发需要在循环,但仅在进入循环的情况下

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

<ul>
  <li>...</li>
  <li>...</li>
  ...
  </ul>

<ul>并且只有在存在某些元素时才</ul>必须打印。

这些是我到目前为止发现的约束:

  1. empty(): 不能用于生成器/迭代器

  2. each(): 已弃用。

  3. iterator_to_array()打败了发电机的优势。

  4. 在循环内部和之后测试的布尔标志不被认为是有效的,因为它会导致在每次迭代时执行该测试,而不是在循环开始和结束时执行一次。

  5. 虽然在上面的示例中可以使用输出缓冲或字符串连接来生成输出,但它不适合循环不会产生任何输出的情况。(感谢@barmar的额外想法)

以下代码片段总结了我们可以迭代的许多不同类型foreach,它可以用作提供答案的开始:

<?php


// Function that iterates on $iterable

function iterateOverIt($iterable) {

    // How to generate the "<ul>"?

    foreach ($iterable as $item) {

        echo "<li>", $item instanceof DateTime ? $item->format("c") : (

            isset($item["col"]) ? $item["col"] : $item

        ), "</li>\n";

    }

    // How to generate the "</ul>"?

}


// Empty array

iterateOverIt([]);

iterateOverIt([1, 2, 3]);


// Empty generator

iterateOverIt((function () : Generator {

    return;

    yield;

})());

iterateOverIt((function () : Generator {

    yield 4;

    yield 5;

    yield 6;

})());


// Class with no public properties

iterateOverIt(new stdClass());

iterateOverIt(new class { public $a = 7, $b = 8, $c = 9;});


$db = mysqli_connect("localhost", "test", "test", "test");

// Empty resultset

iterateOverIt($db->query("SELECT 0 FROM DUAL WHERE false"));

iterateOverIt($db->query("SELECT 10 AS col UNION SELECT 11 UNION SELECT 12"));


// DatePeriod generating no dates

iterateOverIt(new DatePeriod(new DateTime("2020-01-01 00:00:00"), new DateInterval("P1D"), new DateTime("2020-01-01 00:00:00"), DatePeriod::EXCLUDE_START_DATE));

iterateOverIt(new DatePeriod(new DateTime("2020-01-01 00:00:00"), new DateInterval("P1D"), 3, DatePeriod::EXCLUDE_START_DATE));


当年话下
浏览 91回答 3
3回答

慕工程0101907

好吧,这至少是一个有趣的思想实验……class BeforeAfterIterator implements Iterator{&nbsp; &nbsp; private $iterator;&nbsp; &nbsp; public function __construct(iterable $iterator, $before, $after)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (!$iterator instanceof Iterator) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $iterator = (function ($iterable) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yield from $iterable;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })($iterator);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; if ($iterator->valid()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $this->iterator = new AppendIterator();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $this->iterator->append(new ArrayIterator([$before]));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $this->iterator->append($iterator);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $this->iterator->append(new ArrayIterator([$after]));&nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; $this->iterator = new ArrayIterator([]);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; public function current()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return $this->iterator->current();&nbsp; &nbsp; }&nbsp; &nbsp; public function next()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; $this->iterator->next();&nbsp; &nbsp; }&nbsp; &nbsp; public function key()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return $this->iterator->key();&nbsp; &nbsp; }&nbsp; &nbsp; public function valid()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return $this->iterator->valid();&nbsp; &nbsp; }&nbsp; &nbsp; public function rewind()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; $this->iterator->rewind();&nbsp; &nbsp; }}示例用法:function generator() {&nbsp; &nbsp; foreach (range(1, 5) as $x) {&nbsp; &nbsp; &nbsp; &nbsp; yield "<li>$x";&nbsp; &nbsp; }}$beforeAfter = new \BeforeAfterIterator(generator(), '<ul>', '</ul>');foreach ($beforeAfter as $value) {&nbsp; &nbsp; echo $value, PHP_EOL;}输出<ul><li>1<li>2<li>3<li>4<li>5</ul>如果您传递的迭代器没有产生任何值,则不会得到任何输出。见https://3v4l.org/0Xa1a我绝不赞同这是一个好主意,这完全取决于你。我确信有一些更优雅的方法可以使用一些更晦涩的 SPL 类来做到这一点。通过扩展而不是组合来完成它也可能更简单。

呼唤远方

适用于所有情况(PHP >= 7.2)的解决方案是使用 double&nbsp;foreach,其中第一个行为类似于 an&nbsp;if,它不会真正执行循环,但会启动它:function iterateOverIt($iterable) {&nbsp; &nbsp; // First foreach acts like a guard condition&nbsp; &nbsp; foreach ($iterable as $_) {&nbsp; &nbsp; &nbsp; &nbsp; // Pre-processing:&nbsp; &nbsp; &nbsp; &nbsp; echo "<ul>\n";&nbsp; &nbsp; &nbsp; &nbsp; // Real looping, this won't start a *NEW* iteration process but will continue the one started above:&nbsp; &nbsp; &nbsp; &nbsp; foreach ($iterable as $item) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; echo "<li>", $item instanceof DateTime ? $item->format("c") : (&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isset($item["col"]) ? $item["col"] : $item&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ), "</li>\n";&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; // Post-processing:&nbsp; &nbsp; &nbsp; &nbsp; echo "</ul>\n";&nbsp; &nbsp; &nbsp; &nbsp; break;&nbsp; &nbsp; }}3v4l.org 上的完整演示。

青春有我

我认为我们不需要在语言中添加任何新内容。在 PHP 7.1 中,我们收到了一个新类型iterable。使用类型提示,您可以强制您的函数/方法仅接受可以迭代并且在语义上应该被迭代的参数(与不实现的对象相反Traversable)。// Function that iterates on $iterablefunction iterateOverIt(iterable $iterable) {&nbsp; &nbsp; echo '<ul>'.PHP_EOL;&nbsp; &nbsp; foreach ($iterable as $item) {&nbsp; &nbsp; &nbsp; &nbsp; echo "<li>", $item instanceof DateTime ? $item->format("c") : (&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isset($item["col"]) ? $item["col"] : $item&nbsp; &nbsp; &nbsp; &nbsp; ), "</li>\n";&nbsp; &nbsp; }&nbsp; &nbsp; echo '</ul>'.PHP_EOL;}当然,这并不能确保循环至少会迭代一次。在您的测试用例中,您可以简单地使用输出缓冲,但这不适用于仅当循环将迭代至少一次时才需要执行操作的情况。这是不可能的。实现这种连贯性在技术上是不可能的。让我举一个简单的例子:class SideEffects implements Iterator {&nbsp; &nbsp; private $position = 0;&nbsp; &nbsp; private $IHoldValues = [1, 1, 2, 3, 5, 8];&nbsp; &nbsp; public function __construct() {&nbsp; &nbsp; &nbsp; &nbsp; $this->position = 0;&nbsp; &nbsp; }&nbsp; &nbsp; public function doMeBefore() {&nbsp; &nbsp; &nbsp; &nbsp; $this->IHoldValues = [];&nbsp; &nbsp; &nbsp; &nbsp; echo "HAHA!".PHP_EOL;&nbsp; &nbsp; }&nbsp; &nbsp; public function rewind() {&nbsp; &nbsp; &nbsp; &nbsp; $this->position = 0;&nbsp; &nbsp; }&nbsp; &nbsp; public function current() {&nbsp; &nbsp; &nbsp; &nbsp; return $this->IHoldValues[$this->position];&nbsp; &nbsp; }&nbsp; &nbsp; public function key() {&nbsp; &nbsp; &nbsp; &nbsp; return $this->position;&nbsp; &nbsp; }&nbsp; &nbsp; public function next() {&nbsp; &nbsp; &nbsp; &nbsp; ++$this->position;&nbsp; &nbsp; }&nbsp; &nbsp; public function valid() {&nbsp; &nbsp; &nbsp; &nbsp; return isset($this->IHoldValues[$this->position]);&nbsp; &nbsp; }}$it = new SideEffects();if (1/* Some logic to check if the iterator is valid */) {&nbsp; &nbsp; $it->doMeBefore(); // causes side effect, which invalidates previous statement&nbsp; &nbsp; foreach ($it as $row) {&nbsp; &nbsp; &nbsp; &nbsp; echo $row.PHP_EOL;&nbsp; &nbsp; }}正如您在这个不起眼的示例中看到的那样,您的代码中不可能有如此完美的连贯性。我们在 PHP 中有不同的迭代方式的原因是因为没有“一刀切”的解决方案。你试图做的恰恰相反。您正在尝试创建一个解决方案,该解决方案适用于可以迭代的所有内容,即使它可能不应该被迭代。相反,您应该编写可以适当处理所有方法的代码。function iterateOverIt(iterable $iterable) {&nbsp; &nbsp; if (is_array($iterable)) {&nbsp; &nbsp; &nbsp; &nbsp; echo 'The parameter is an array'.PHP_EOL;&nbsp; &nbsp; &nbsp; &nbsp; if ($iterable) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // foreach&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; } elseif ($iterable instanceof Iterator) {&nbsp; &nbsp; &nbsp; &nbsp; echo 'The parameter is a traversable object'.PHP_EOL;&nbsp; &nbsp; &nbsp; &nbsp; /**&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;* @var \Iterator&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp; &nbsp; &nbsp; $iterable = $iterable;&nbsp; &nbsp; &nbsp; &nbsp; if ($iterable->valid()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // foreach&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; } elseif ($iterable instanceof Traversable) {&nbsp; &nbsp; &nbsp; &nbsp; echo 'The parameter is a traversable object'.PHP_EOL;&nbsp; &nbsp; &nbsp; &nbsp; // I donno, some hack?&nbsp; &nbsp; &nbsp; &nbsp; foreach ($iterable as $i) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // do PRE&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; foreach ($iterable as $el) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // foreach calls reset first on traversable objects. Each loop should start anew&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // do POST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; // throw new Exception?&nbsp; &nbsp; }}如果您真的想要,您甚至可以使用 将普通对象包含在其中is_object(),但正如我所说,除非它实现,否则Traversable不要对其进行迭代。
打开App,查看更多内容
随时随地看视频慕课网APP