生成器语法

生成器函数看起来像普通函数——不同的是普通函数返回一个值,而生成器可以 yield 生成多个想要的值。 任何包含 yield 的函数都是一个生成器函数。

当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用对象的遍历方法,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。

一旦不再需要产生更多的值,生成器可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。

注意:

生成器能够返回多个值,通过 Generator::getReturn() 可以获取到。

yield关键字

生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。

示例 #1 一个简单的生成值的例子

<?php
function gen_one_to_three() {
    for (
$i 1$i <= 3$i++) {
        
//注意变量$i的值在不同的yield之间是保持传递的。
        
yield $i;
    }
}

$generator gen_one_to_three();
foreach (
$generator as $value) {
    echo 
"$value\n";
}
?>

以上例程会输出:

1
2
3

注意:

在内部会为生成的值配对连续的整型索引,就像一个非关联的数组。

警告

传入 Generator::send() 的值会被赋值到 $data, 或者直接调用 Generator::next() 时,赋的值将是 null

指定键名来生成值

PHP的数组支持关联键值对数组,生成器也一样支持。所以除了生成简单的值,你也可以在生成值的时候指定键名。

如下所示,生成一个键值对与定义一个关联数组十分相似。

示例 #2 生成一个键值对

<?php
/* 
 * 下面每一行是用分号分割的字段组合,第一个字段将被用作键名。
 */

$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOF;

function 
input_parser($input) {
    foreach (
explode("\n"$input) as $line) {
        
$fields explode(';'$line);
        
$id array_shift($fields);

        yield 
$id => $fields;
    }
}

foreach (
input_parser($input) as $id => $fields) {
    echo 
"$id:\n";
    echo 
"    $fields[0]\n";
    echo 
"    $fields[1]\n";
}
?>

以上例程会输出:

1:
    PHP
    Likes dollar signs
2:
    Python
    Likes whitespace
3:
    Ruby
    Likes blocks
警告

和之前生成简单值类型一样,在一个表达式上下文中生成键值对也需要使用圆括号进行包围:

$data = (yield $key => $value);

生成 null 值

Yield 可以在没有参数传入的情况下被调用来生成一个 null 值并配对一个自动的键名。

示例 #3 生成nulls

<?php
function gen_three_nulls() {
    foreach (
range(13) as $i) {
        yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));
?>

以上例程会输出:

array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

使用引用来生成值

生成函数可以像使用值一样来使用引用生成。这个和从函数返回一个引用一样:通过在函数名前面加一个引用符号。

示例 #4 使用引用来生成值

<?php
function &gen_reference() {
    
$value 3;

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

/* 
 * 我们可以在循环中修改 $number 的值,而生成器是使用的引用值来生成,所以 gen_reference() 内部的 $value 值也会跟着变化。
 */
foreach (gen_reference() as &$number) {
    echo (--
$number).'... ';
}
?>

以上例程会输出:

2... 1... 0... 

Generator delegation via yield from

Generator delegation allows you to yield values from another generator, Traversable object, or array by using the yield from keyword. The outer generator will then yield all values from the inner generator, object, or array until that is no longer valid, after which execution will continue in the outer generator.

If a generator is used with yield from, the yield from expression will also return any value returned by the inner generator.

警告

Storing into an array (e.g. with iterator_to_array())

yield from does not reset the keys. It preserves the keys returned by the Traversable object, or array. Thus some values may share a common key with another yield or yield from, which, upon insertion into an array, will overwrite former values with that key.

A common case where this matters is iterator_to_array() returning a keyed array by default, leading to possibly unexpected results. iterator_to_array() has a second parameter use_keys which can be set to false to collect all the values while ignoring the keys returned by the Generator.

示例 #5 yield from with iterator_to_array()

<?php
 
function inner() {
     yield 
1// key 0
     
yield 2// key 1
     
yield 3// key 2
 
}
 function 
gen() {
     yield 
0// key 0
     
yield from inner(); // keys 0-2
     
yield 4// key 1
 
}
 
// pass false as second parameter to get an array [0, 1, 2, 3, 4]
 
var_dump(iterator_to_array(gen()));
 
?>

以上例程会输出:

 array(3) {
   [0]=>
   int(1)
   [1]=>
   int(4)
   [2]=>
   int(3)
 }
 

示例 #6 yield from 的基本用法

<?php
function count_to_ten() {
    yield 
1;
    yield 
2;
    yield from [
34];
    yield from new 
ArrayIterator([56]);
    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 ";
}
?>

以上例程会输出:

1 2 3 4 5 6 7 8 9 10 

示例 #7 yield from 并返回多个值

<?php
function count_to_ten() {
    yield 
1;
    yield 
2;
    yield from [
34];
    yield from new 
ArrayIterator([56]);
    yield from 
seven_eight();
    return yield from 
nine_ten();
}

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();
?>

以上例程会输出:

1 2 3 4 5 6 7 8 9 10