©
本文档使用 PHP中文网手册 发布
一个生成器函数看起来像一个普通的函数,不同的是普通函数返回一个值,而一个生成器可以yield生成许多它所需要的值。
当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。
一旦不再需要产生更多的值,生成器函数可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。
Note:
一个生成器不可以返回值: 这样做会产生一个编译错误。然而return空是一个有效的语法并且它将会终止生成器继续执行。
生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。
Example #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
Note:
在内部会为生成的值配对连续的整型索引,就像一个非关联的数组。
如果在一个表达式上下文(例如在一个赋值表达式的右侧)中使用yield,你必须使用圆括号把yield申明包围起来。 例如这样是有效的:
$data = (yield $value);
而这样就不合法,并且在PHP5中会产生一个编译错误:
$data = yield $value;
The parenthetical restrictions do not apply in PHP 7.
这个语法可以和生成器对象的 Generator::send() 方法配合使用。
PHP的数组支持关联键值对数组,生成器也一样支持。所以除了生成简单的值,你也可以在生成值的时候指定键名。
如下所示,生成一个键值对与定义一个关联数组十分相似。
Example #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);
Yield可以在没有参数传入的情况下被调用来生成一个 NULL
值并配对一个自动的键名。
Example #3 生成 NULL
s
<?php
function gen_three_nulls () {
foreach ( range ( 1 , 3 ) as $i ) {
yield ;
}
}
var_dump ( iterator_to_array ( gen_three_nulls ()));
?>
以上例程会输出:
array(3) { [0]=> NULL [1]=> NULL [2]=> NULL }
生成函数可以像使用值一样来使用引用生成。这个和returning references from functions(从函数返回一个引用)一样:通过在函数名前面加一个引用符号。
Example #4 使用引用来生成值
<?php
function & gen_reference () {
$value = 3 ;
while ( $value > 0 ) {
yield $value ;
}
}
foreach ( gen_reference () as & $number ) {
echo (-- $number ). '... ' ;
}
?>
以上例程会输出:
2... 1... 0...
In PHP 7, 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.
Example #5 Basic use of yield from
<?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 " ;
}
?>
以上例程会输出:
1 2 3 4 5 6 7 8 9 10
Example #6 yield from and return values
<?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 ();
}
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
[#1] zilvinas at kuusas dot lt [2015-11-16 09:18:10]
Do not call generator functions directly, that won't work.
<?php
function my_transform($value) {
var_dump($value);
return $value * 2;
}
function my_function(array $values) {
foreach ($values as $value) {
yield my_transform($value);
}
}
$data = [1, 5, 10];
// my_transform() won't be called inside my_function()
my_function($data);
# my_transform() will be called.
foreach (my_function($data) as $val) {
// ...
}
?>
[#2] Harun Yasar harunyasar at mail dot com [2015-06-12 14:43:11]
That is a simple fibonacci generator.
<?php
function fibonacci($item) {
$a = 0;
$b = 1;
for ($i = 0; $i < $item; $i++) {
yield $a;
$a = $b - $a;
$b = $a + $b;
}
}
# give me the first ten fibonacci numbers
$fibo = fibonacci(10);
foreach ($fibo as $value) {
echo "$value\n";
}
?>
[#3] info at boukeversteegh dot nl [2015-01-23 19:02:58]
[This comment replaces my previous comment]
You can use generators to do lazy loading of lists. You only compute the items that are actually used. However, when you want to load more items, how to cache the ones already loaded?
Here is how to do cached lazy loading with a generator:
<?php
class CachedGenerator {
protected $cache = [];
protected $generator = null;
public function __construct($generator) {
$this->generator = $generator;
}
public function generator() {
foreach($this->cache as $item) yield $item;
while( $this->generator->valid() ) {
$this->cache[] = $current = $this->generator->current();
$this->generator->next();
yield $current;
}
}
}
class Foobar {
protected $loader = null;
protected function loadItems() {
foreach(range(0,10) as $i) {
usleep(200000);
yield $i;
}
}
public function getItems() {
$this->loader = $this->loader ?: new CachedGenerator($this->loadItems());
return $this->loader->generator();
}
}
$f = new Foobar;
# First
print "First\n";
foreach($f->getItems() as $i) {
print $i . "\n";
if( $i == 5 ) {
break;
}
}
# Second (items 1-5 are cached, 6-10 are loaded)
print "Second\n";
foreach($f->getItems() as $i) {
print $i . "\n";
}
# Third (all items are cached and returned instantly)
print "Third\n";
foreach($f->getItems() as $i) {
print $i . "\n";
}
?>
[#4] dejiakala at gmail dot com [2014-10-05 20:31:52]
Another Fibonacci sequence with yield keyword:
<?php
function getFibonacci($first, $second, $total) {
yield $first;
yield $second;
for ($i = 1, $total -= 2; $i <= $total; $i++) {
$sum = $first + $second;
$first = $second;
$second = $sum;
yield $sum;
}
}
// Generate first 10 numbers of the Fibonacci sequence starting from 0, 1
foreach (getFibonacci(0, 1, 10) as $fibonacci) {
// 0 1 1 2 3 5 8 13 21 34
echo $fibonacci . " ";
}
?>
[#5] christophe dot maymard at gmail dot com [2014-09-11 16:43:48]
<?php
//Example of class implementing IteratorAggregate using generator
class ValueCollection implements IteratorAggregate
{
private $items = array();
public function addValue($item)
{
$this->items[] = $item;
return $this;
}
public function getIterator()
{
foreach ($this->items as $item) {
yield $item;
}
}
}
//Initializes a collection
$collection = new ValueCollection();
$collection
->addValue('A string')
->addValue(new stdClass())
->addValue(NULL);
foreach ($collection as $item) {
var_dump($item);
}
[#6] denshadewillspam at HOTMAIL dot com [2014-04-13 10:46:39]
Note that you can't use count() on generators.
function xrange() {
for ($a = 0; $a < 10; $a++)
{
yield $a;
}
}
function mycount(Traversable $traversable)
{
$skip = 0;
foreach($traversable as $skip)
{
$skip++;
}
return $skip;
}
echo "Count:" . count(xrange()). PHP_EOL;
echo "Count:" . mycount(xrange()). PHP_EOL;
[#7] Shumeyko Dmitriy [2013-11-08 07:46:23]
This is little example of using generators with recursion. Used version of php is 5.5.5
[php]
<?php
define ("DS", DIRECTORY_SEPARATOR);
define ("ZERO_DEPTH", 0);
define ("DEPTHLESS", -1);
define ("OPEN_SUCCESS", True);
define ("END_OF_LIST", False);
define ("CURRENT_DIR", ".");
define ("PARENT_DIR", "..");
function DirTreeTraversal($DirName, $MaxDepth = DEPTHLESS, $CurrDepth = ZERO_DEPTH)
{
if (($MaxDepth === DEPTHLESS) || ($CurrDepth < $MaxDepth)) {
$DirHandle = opendir($DirName);
if ($DirHandle !== OPEN_SUCCESS) {
try{
while (($FileName = readdir($DirHandle)) !== END_OF_LIST) { //read all file in directory
if (($FileName != CURRENT_DIR) && ($FileName != PARENT_DIR)) {
$FullName = $DirName.$FileName;
yield $FullName;
if(is_dir($FullName)) { //include sub files and directories
$SubTrav = DirTreeTraversal($FullName.DS, $MaxDepth, ($CurrDepth + 1));
foreach($SubTrav as $SubItem) yield $SubItem;
}
}
}
} finally {
closedir($DirHandle);
}
}
}
}
$PathTrav = DirTreeTraversal("C:".DS, 2);
print "<pre>";
foreach($PathTrav as $FileName) printf("%s\n", $FileName);
print "</pre>";
[/php]
[#8] Adil lhan (adilmedya at gmail dot com) [2013-04-18 09:02:52]
For example yield keyword with Fibonacci:
function getFibonacci()
{
$i = 0;
$k = 1; //first fibonacci value
yield $k;
while(true)
{
$k = $i + $k;
$i = $k - $i;
yield $k;
}
}
$y = 0;
foreach(getFibonacci() as $fibonacci)
{
echo $fibonacci . "\n";
$y++;
if($y > 30)
{
break; // infinite loop prevent
}
}