Skip to content

生成器

生成器也是一个函数,不同的是生成器允许你使用 yield 依次的返回函数中的值,而不是一次性的返回所有的值,这也正是生成器降低性能开销的原因。

试着比较,同样是生成一定范围内的值的差异 :

php
//生成器的实现
function xrange($start,$end,$step=1)
{
	for ($i=$start; $i $i;
	}
}

foreach (xrange(1,1000) as $key => $value) {
	echo $key.'------>'.$value.PHP_EOL;
}

//传统的实现
foreach (range(1,1000) as $key => $value) {
	echo $key.'------>'.$value.PHP_EOL;
}

相比原生 range 函数返回的是一个包含所有值的数组,生成器并不会直接返回你所需要的值,取而代之的是返回值是一个生成器对象。该对象继承了 Iterator 接口,因此你可以像访问普通 Iterator 实现一样去访问生成器,从而获取你所需要的值。当你遍历这个生成器对象的时候生成器会自动的调用生成器函数返回你所需要的值,并自动的保存生成器的状态,在你需要产生下一个只的时候恢复调用。

yield

也正如你所看到的生成器内部使用了 yield 关键字返回了多个你所需要的值并且不会中断函数的执行,而普通函数却只能一次性的返回一个值,且会中断函数的执行。

在一个上下文环境中你可能需要使用生成器返回的值,这一点也很容易的做到,你只需要使用$res = (yield 'value')即可,这里的括号在 PHP5 中是必选的,在 PHP7 的版本中你可以省略他。如果你想获得一个键值对的形式,这样做也很简单,你只需要像$res = (yield 'key'=>'value')这样的使用即可。有时候你可能需要返回 Null 值,你只需要直接使用 yield 即可。

协程

PHP 的协程是建立在生成器和 yield 关键字基础之上的。他的强大之处在于他提供了调用者和被调用者的双向数据通信能力,这一点使用生成器对象的 send 方法和 yield 返回值的支持可以很容易的做到。

php
function generator() {
    $ret = (yield 'this is from yield 1');
    var_dump($ret);
    $ret = (yield 'this is from yield 2');
    var_dump($ret);
}

$generator = generator();
var_dump($generator->current()); // string(6) "this is from yield 1"
var_dump($generator->send('this is a message')); // string(4) "this is a message"
                                                // string(6) "his is from yield 2" (the var_dump of the ->send() return value)
var_dump($generator->send('this is antoher message')); // string(4) "this is antoher message"   (again from within generator)
                                                      // NULL

从上边的例子也不难看出,当你第一次调用生成器函数时,生成器会自动的执行第一个 yield,这也就是为什么$generator->current()的返回值是 this is from yield 1 的原因了。

默认情况下 send 的值将作为当前生成器所在 yield 的返回值返回。

php
function printer() {
    while (true) {
        $string = yield;
        echo $string;
    }
}

$printer = printer();
$printer->send('Hello world!');

生成器函数还可以像使用引用值一样使用引用生成如:

php
function &gen_reference() {
    $value = 3;

    while ($value > 0) {
        yield $value;
    }
}

/*
 * 由于这里使用了引用生成函数也就是在生成函数前加了&,在迭代时使用&引用改变了迭代值也会引起了生成函数内部值的改变。
 */
foreach (gen_reference() as &$number) {
    echo (--$number).'... ';
}

在 PHP7 系列的版本中,生成器允许你使用 yield from 返回一个另一个迭代器对象或者其他实现了 Traversable 接口的对象又或者 array 数组,在迭代的时候外部的生成器将会自动的 yeild 调用迭代内部的生成器对象。

php
function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    yield 9;
    yield 10;
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

foreach (count_to_ten() as $num) {
    echo "$num ";
}

在使用了 yield from 的时候,如果你在内部的生成器中使用了 return 返回值,那么这个返回值也将会自动的被外部的迭代器返回。

php
function count_to_ten() {
    yield 1;
    yield 2;
    yield from [3, 4];
    yield from new ArrayIterator([5, 6]);
    yield from seven_eight();
    return yield from nine_ten(); //这里内部调用的return值和yield值一起被返回了
}

function seven_eight() {
    yield 7;
    yield from eight();
}

function eight() {
    yield 8;
}

function nine_ten() {
    yield 9;
    return 10;
}

$gen = count_to_ten();
foreach ($gen as $num) {
    echo "$num ";
}
echo $gen->getReturn();

本文的部分示例来自于php 官网,另外本文的形成离不开鸟哥的在 PHP 中使用协程实现多任务调度一文,在此表示感谢。

本文的主要参考链接:

http://php.net/manual/zh/language.generators.php

http://www.laruence.com/2015/05/28/3038.html