PHP 生成器用法介绍

初识生成器

说到 PHP 中的生成器,很多人都知道它是一种可以用来高效迭代的工具,比如:

    <?php
    function xrange($start, $end, $step = 1) {
        for ($i = $start; $i <= $end; $i += $step) {
            yield $i;
        }
    }

    foreach (xrange(1, 1000000) as $num) {
        echo $num, "\n";
    }

xrange() 实现了 range() 的功能,但是 range() 返回的是一个数组,占据了对应的内存空间,而 xrange 返回的是一个生成器,在生成器内部的循环中,一次一次将数字传递出来。

当然,也可以不通过生成器来实现这个功能,而是可以通过实现 Iterator 接口。但是使用 Generator 类型,其已经实现了 Iterator 接口,因此我们可以直接使用它来进行迭代。

理解生成器

可以把生成器简单地看成是可中断的函数,执行过程执行到了 yield 语句,则会将控制器从该函数返回给调用者。

看看下面的例子:

$gen = call_user_func(function (){
    yield 'Hello, generator stops at the first yield' . '<br/>';
    yield 'Hello, generator stops at the second yield' . '<br/>';
});

var_dump($range instanceof Generator); // bool(true)
var_dump($gen instanceof Iterator); // bool(true)

$gen->rewind();
echo $gen->current(); // Hello, generator stops at the first yield
$gen->next();
echo $gen->current(); // Hello, generator stops at the second yield
$gen->next();
var_dump($gen->valid()) // false

执行 rewind() 时,生成器开始执行并停在第一个 yield 处。调用 current(),可以返回当前生成器产生的值,即第一个 yield 处的值。

调用 next() 后,生成器运行到第二个 yield 处,同样,调用 current() 将返回在第二个 yield 处返回的值。

第二次执行 next() 后,生成器从第二个 yield 处执行到整个生产器的终点,此时 valid() 将返回 false。

那么在第一个例子中,foreach() 可以看成是不断地在调用 current() 和 next(),直到 valid() 返回 false。

注意, 这里的 rewind() 是可以省略的,因为第一次调用 current() 时,生成器就会执行到第一个 yield 处,相当于执行了 rewind()。

同时,只能在生成器还没开始执行的时候调用 rewind(),否则就会报错:

( ! ) Fatal error: Uncaught Exception: Cannot rewind a generator that was already run

既然如此,rewind() 到底有什么实际用呢?看下面代码就知道了:

function getLines($file) {
    $f = fopen($file, 'r');
    try {
        while ($line = fgets($f)) {
            yield $line;
        }
    } finally {
        fclose($f);
    }
}

我们可以通过调用 rewind() 来执行 fopen(),如果打开文件失败的话,就会提前报出错误。

双向通信

生成器不仅可以将数据返回给调用者,同时,也可以接收调用者发来的数据,下面的代码展示了方法:


$gen = call_user_func(function (){

    $words = yield 'Hello, generator stops at the first yield' . '<br/>';
    echo $words . ' from first yield' . '<br/>';

    $words = yield 'Hello, generator stops at the second yield' . '<br/>';
    echo $words . ' from second yield' . '<br/>';

});


echo $gen->current();
$gen->send('Hi, plz continue to run');

echo $gen->current();
$gen->send('Hi, plz continue to run');

/*
结果:
Hello, generator stops at the first yield
Hi, plz continue to run from first yield
Hello, generator stops at the second yield
Hi, plz continue to run from second yield
*/

可以看到,这里的 yield 不仅是一个语句,同时也作为一个表达式来使用,send() 会将参数值传递到生成器中当前的 yield 处,并且会让生成器运行到下一个 yield,相当于调用了 next()。yield接收到的值保存在变量$words中。

猜你喜欢

转载自blog.csdn.net/stfphp/article/details/72814795