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

标签 php loops foreach iterable php-7.4

这有点类似于问题:How to determine the first and last iteration in a foreach loop? ,然而,这个 10 岁以上的问题很严重 array面向并且没有一个答案与可以循环许多不同类型的事实兼容。

给定一个可以在 PHP >= 7.4 中迭代的循环(数组,迭代器,生成器,PDOStatementDatePeriod,对象属性,...),我们如何以有效的方式触发需要的代码在循环之前/之后发生,但只有在循环进入的情况下 ?

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

<ul>
  <li>...</li>
  <li>...</li>
  ...
</ul>
<ul></ul>必须打印只有如果有一些元素。

这些是我目前发现的限制:
  • empty() : 不能用于 generators/iterators .
  • each() : 已弃用。
  • iterator_to_array() 打败了发电机的优势。
  • 在循环内部和循环之后测试的 bool 标志不被认为是有效的,因为它会导致该测试在每次迭代时执行,而不是在循环开始和结束时执行一次。
  • 虽然在上面的示例中可以使用输出缓冲或字符串连接来生成输出,但它不适合循环不会产生任何输出的情况。 (感谢 @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>
    

    最佳答案

    我认为我们不需要在语言中添加任何新内容。在 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不要迭代它。

    关于php - 如何在 foreach 循环之前/之后触发代码,仅当在 PHP 7.4+ 中以有效方式输入此循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60494732/

    相关文章:

    c++ - 为什么我的C++代码不接受输入?我的代码有什么问题?

    r - 修复具有不同内核数的并行仿真运行的种子

    php - 基于区域选择的 osclass 城市下拉菜单

    php - 在 php 或 javascript 中构建 html 结构?

    php - 用户已经有超过 'max_user_connections' 个事件连接

    php正则表达式获取href标签内的自定义url和字符串

    循环运行时出现 Java 逻辑错误 : If/else counter incorrectly counting

    php - 如何在某些条件下在 foreach 循环中添加 css 类

    json - 使用 Perl 循环遍历 JSON 数据并打印出一个字段(不是 HASH 引用)

    jquery - jQuery 中的异步 foreach