你应该知道的 PHP yield 知识 ( 中 )
在 你应该知道的 PHP 生成器知识 ( 一 ) 中,我们可以发现,一个生成器函数和一个普通函数的最大区别,就是使用了 yield
语句。所以,掌握生成器,最重要的就是掌握 yield
语句
生成器语法
生成器从外观上看起来像是一个普通的函数,不同的是普通函数会一次性返回所有的序列值,使用相同的参数再次调用,返回的值和前一次相同。( 这是函数的基本要求 )。但生成器就不一样了,生成器一次只返回一个序列中的一个值。并在相同参数的再次调用,返回的是序列中的下一个值。
从另一个方面说,当一个生成器被调用的时候,它返回一个可以被遍历的对象。当我们使用 foreach
遍历这个对象的时候,PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态。这样,在下一次调用的时候就可以恢复调用的状态。
一旦不需要生成更多的值,或者生成器已经不能生成更多的值,那么可以抛出一个 LogicException
异常简单的退出。调用生成器的代码会捕获这个异常,然后继续执行,就像一个数组已经被遍历完了一样。
注意
在 PHP 5 中,生成器函数不能用 return 语句显式的返回一个值,这回引发一个编译器错误。但空返回(
return;
)是允许的,一般用于终止生成器。在 PHP 7 中,生成器函数可以使用 return 语句返回任意值,然后可以通过
Generator::getReturn()
方法来获取。
PHP yield 语句
一个生成器的核心是 yield
关键字。它最简单的使用方式看起来就是 return
语句,一个返回某个值的 return
语句。不同之处在于普通的 return
语句会返回值并终止函数的执行,而 yield 语句也会返回一个值,但只是简单的暂停此生成器函数的执行。这个不同造就了上面我们所提到的普通函数与生成器的差别。
我们一个范例来演示下 yield 语句这种特殊的返回暂停模式
<?php function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { //注意变量 $i 的值在不同的 yield 之间是保持传递的。 echo "生成一个值 $i \n"; yield $i; } } $generator = gen_one_to_three(); foreach ($generator as $value) { echo "$value\n"; }
运行结果如下
[yufei@www.twle.cn helloworld]$ php d.php 生成一个值 1 1 生成一个值 2 2 生成一个值 3 3
对比下输出语句,foreach 语句每次对生成器函数的调用只返回一个值。
表达式语句中的 yield
我们还可以将 yield 语句生成的值赋值给一个变量。但需要注意的是,如果在一个表达式上下文(例如在一个赋值表达式的右侧)中使用 yield,必须使用圆括号把 yield 申明包围起来。 例如这样是有效的
$data = (yield $value);
但下面这样就不合法,在 PHP5 中会产生一个编译错误
$data = yield $value;
但在 PHP 中,圆括号是可以省略的,上面这条语句也不会报错。而且这种语法特别有用,因为它可以和 Generator::send()
函数配合使用。
yield 生成一个键值对
PHP 中的生成器,除了生成简单的值外,还可以生成一个键值对。生成键值对的方式和定义一个关联数组的方式十分相似,如下所示
<?php function gen_alpha() { $alpha = explode(' ', 'a b c d e f g h i j k l m n o p q r s t u v w x y z'); for ($i = 1; $i <= 10; $i++) { yield $i => $alpha[$i%26]; } } $generator = gen_alpha(); foreach ($generator as $k => $v) { echo $k, " => ", $v, "\n"; }
运行结果如下
[yufei@www.twle.cn helloworld]$ php d.php 1 => b 2 => c 3 => d 4 => e 5 => f 6 => g 7 => h 8 => i 9 => j 10 => k
注意
和表达式语句中的 yield 一样,在一个表达式上下文中生成键值对也需要使用圆括号进行包围,例如
$data = (yield $key => $value);
空 yield 语句
一个空 yield
语句将会返回一个 NULL
值。
例如下面这段代码演示的
<?php function gen_three_nulls() { foreach (range(1, 3) as $i) { yield; } } foreach ( gen_three_nulls() as $v ) { var_dump($v); }
运行结果如下
[yufei@www.twle.cn helloworld]$ php d.php NULL NULL NULL
yield 生成一个引用值
生成函数可以像使用值一样来使用引用生成。使用方式和从函数返回一个引用一样:通过在函数名前面加一个引用符号。
通过这种引用生成的方式,我们可以在生成器外部控制生成器的退出时机。
<?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...