5

これは、次の質問に似ています: 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. ループ内およびループ後にテストされるブール値フラグは、効率的とは見なされません。そのテストは、ループの開始時と終了時に 1 回ずつ実行されるのではなく、反復ごとに実行されることになるためです。
  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));

このようなスクリプトは、次の出力になります。

<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>
4

3 に答える 3

0

すべてのケース(PHP >= 7.2)で機能する解決策は double を使用することですforeach。最初の 1 つは のように機能し、実際にはループを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;
    }
}

3v4l.org の完全なデモ

于 2020-03-03T09:44:21.427 に答える