生成器语法

生成器函数看起来像普通函数——不同的是普通函数返回一个值,而生成器可以 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... 

可用的 yield from 生成器委托

生成器委托允许使用 yield from 关键字从另外一个生成器、 Traversable 对象、array 通过生成值。 外部生成器将从内部生成器、object、array 中生成所有的值,直到它们不再有效, 之后将在外部生成器中继续执行。

如果生成器与 yield from 一起使用,那么 yield from 表达式将返回内部生成器返回的任何值。

警告

存储到 array (例如使用 iterator_to_array()

yield from 不能重置 key。它保留 Traversable 对象或者 array 返回的 key。因此,某些值可能会与其他的 yield 或者 yield from 共享公共的 key,因此,在插入数组时将会用这个 key 覆盖以前的值。

一个非常重要的常见情况是 iterator_to_array() 默认返回带 key 的 array , 这可能会造成无法预料的结果。 iterator_to_array() 还有第二个参数 use_keys ,可以设置为 false 来收集 Generator 返回的不带 key 的所有值。

示例 #5 使用 iterator_to_array()yield from

<?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
 
}
 
// 传递 false 作为第二个参数获得数组 [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