如何在 foreach 循环之前/之后触发代码,仅当在 PHP 7.4+ 中以有效方式输入此循环?

Pat*_*ert 5 php foreach loops iterable php-7.4

这有点类似于问题:如何确定 foreach 循环中的第一次和最后一次迭代?,然而,这个 10 岁以上的问题是非常array面向的,没有一个答案与可以循环许多不同类型的事实相兼容。

鉴于一些循环,可以迭代在PHP> = 7.4(数组,迭代器,发电机,PDOStatementDatePeriod,对象的属性,......),我们怎么可以触发,以高效的方式,代码需要发生前/后循环,但仅在进入循环的情况下

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

<ul>
  <li>...</li>
  <li>...</li>
  ...
</ul>
Run Code Online (Sandbox Code Playgroud)

<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));
Run Code Online (Sandbox Code Playgroud)

这样的脚本应该产生以下输出:

<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>
Run Code Online (Sandbox Code Playgroud)

Pat*_*ert 0

适用于所有情况(PHP >= 7.2)的解决方案是使用 double foreach,其中第一个的作用类似于if,它不会真正执行循环,但会启动它:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

3v4l.org 上有完整演示