亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何在 foreach 循環之前/之后觸發代碼,只有在 PHP 7.4+ 中以一種有效的方式輸入這

如何在 foreach 循環之前/之后觸發代碼,只有在 PHP 7.4+ 中以一種有效的方式輸入這

PHP
當年話下 2022-10-14 14:31:02
這有點類似于問題:如何確定 foreach 循環中的第一次和最后一次迭代?,但是,這個 +10 歲的問題非常有array針對性,并且沒有一個答案與可以循環許多不同類型的事實兼容。給定一個可以在 PHP >= 7.4中迭代PDOStatement的東西(數組、迭代器、生成器、DatePeriod...、對象屬性等)的循環,我們如何以有效的方式觸發需要在循環,但僅在進入循環的情況下?一個典型的用例可能是生成 HTML 列表:<ul>   <li>...</li>   <li>...</li>   ...   </ul><ul>并且只有在存在某些元素時才</ul>必須打印。這些是我到目前為止發現的約束:empty(): 不能用于生成器/迭代器。each(): 已棄用。iterator_to_array()打敗了發電機的優勢。在循環內部和之后測試的布爾標志不被認為是有效的,因為它會導致在每次迭代時執行該測試,而不是在循環開始和結束時執行一次。雖然在上面的示例中可以使用輸出緩沖或字符串連接來生成輸出,但它不適合循環不會產生任何輸出的情況。(感謝@barmar的額外想法)以下代碼片段總結了我們可以迭代的許多不同類型foreach,它可以用作提供答案的開始:<?php// Function that iterates on $iterablefunction 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 arrayiterateOverIt([]);iterateOverIt([1, 2, 3]);// Empty generatoriterateOverIt((function () : Generator {    return;    yield;})());iterateOverIt((function () : Generator {    yield 4;    yield 5;    yield 6;})());// Class with no public propertiesiterateOverIt(new stdClass());iterateOverIt(new class { public $a = 7, $b = 8, $c = 9;});$db = mysqli_connect("localhost", "test", "test", "test");// Empty resultsetiterateOverIt($db->query("SELECT 0 FROM DUAL WHERE false"));iterateOverIt($db->query("SELECT 10 AS col UNION SELECT 11 UNION SELECT 12"));// DatePeriod generating no datesiterateOverIt(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));
查看完整描述

3 回答

?
慕工程0101907

TA貢獻1887條經驗 獲得超5個贊

好吧,這至少是一個有趣的思想實驗……


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;

}

輸出


<ul>

<li>1

<li>2

<li>3

<li>4

<li>5

</ul>

如果您傳遞的迭代器沒有產生任何值,則不會得到任何輸出。


https://3v4l.org/0Xa1a

我絕不贊同這是一個好主意,這完全取決于你。我確信有一些更優雅的方法可以使用一些更晦澀的 SPL 類來做到這一點。通過擴展而不是組合來完成它也可能更簡單。


查看完整回答
反對 回復 2022-10-14
?
呼喚遠方

TA貢獻1856條經驗 獲得超11個贊

適用于所有情況(PHP >= 7.2)的解決方案是使用 double foreach,其中第一個行為類似于 an 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 上的完整演示。



查看完整回答
反對 回復 2022-10-14
?
青春有我

TA貢獻1784條經驗 獲得超8個贊

我認為我們不需要在語言中添加任何新內容。在 PHP 7.1 中,我們收到了一個新類型iterable。使用類型提示,您可以強制您的函數/方法僅接受可以迭代并且在語義上應該被迭代的參數(與不實現的對象相反Traversable)。


// Function that iterates on $iterable

function iterateOverIt(iterable $iterable) {

    echo '<ul>'.PHP_EOL;

    foreach ($iterable as $item) {

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

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

        ), "</li>\n";

    }

    echo '</ul>'.PHP_EOL;

}

當然,這并不能確保循環至少會迭代一次。在您的測試用例中,您可以簡單地使用輸出緩沖,但這不適用于僅當循環將迭代至少一次時才需要執行操作的情況。這是不可能的。實現這種連貫性在技術上是不可能的。讓我舉一個簡單的例子:


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;

    }

}

正如您在這個不起眼的示例中看到的那樣,您的代碼中不可能有如此完美的連貫性。


我們在 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(),但正如我所說,除非它實現,否則Traversable不要對其進行迭代。


查看完整回答
反對 回復 2022-10-14
  • 3 回答
  • 0 關注
  • 141 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號