生成器语法

生成器函数看起来就像普通函数一样,只是它不是返回一个值,而是一个生成器 yield 尽可能多的值。任何包含 yield 的函数都是生成器函数。

当调用生成器函数时,它会返回一个可以迭代的对象。当您迭代该对象(例如,通过 foreach 循环)时,PHP 会在每次需要值时调用对象的迭代方法,然后在生成器生成一个值时保存生成器的状态,以便在需要下一个值时恢复。

一旦没有更多值需要生成,那么生成器就可以简单地返回,调用代码就会继续执行,就像数组用尽了值一样。

注意:

生成器可以返回值,这些值可以使用 Generator::getReturn() 检索。

yield 关键字

生成器函数的核心是 yield 关键字。在最简单的形式中,yield 语句看起来很像 return 语句,只是它不是停止执行函数并返回,而是向循环遍历生成器的代码提供一个值,并暂停生成器函数的执行。

示例 #1 生成值的简单示例

<?php
function gen_one_to_three() {
for (
$i = 1; $i <= 3; $i++) {
// 注意 $i 在生成之间保持不变。
yield $i;
}
}

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

上面的例子将输出

1
2
3

注意:

在内部,顺序整型键将与生成的 value 配对,就像非关联数组一样。

生成带键的值

PHP 还支持关联数组,生成器也不例外。除了生成上面显示的简单值之外,您还可以同时生成一个键。

生成键/值对的语法与用于定义关联数组的语法非常相似,如下所示。

示例 #2 生成键/值对

<?php
/*
* 输入是分号分隔的字段,第一个字段是作为键使用的 ID。
*/

$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

生成 null 值

yield 可以不带参数调用,以生成一个 **null** 值,并带有自动键。

示例 #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
}

通过引用生成

生成器函数能够通过引用生成值,以及通过值生成值。这与 从函数返回引用 的方式相同:在函数名称之前添加一个取地址符。

示例 #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 中生成值。然后,外部生成器将生成内部生成器、对象或数组中的所有值,直到不再有效,之后执行将在外部生成器中继续。

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

警告

存储到数组中(例如,使用 iterator_to_array()

yield from 不会重置键。它保留 Traversable 对象或 array 返回的键。因此,一些值可能与另一个 yieldyield from 共享一个键,这将在插入到数组中时,用该键覆盖以前的值。

这很重要的一种常见情况是 iterator_to_array() 默认情况下返回一个带键的数组,从而导致可能意想不到的结果。 iterator_to_array() 有一个第二个参数 preserve_keys,可以将其设置为 **false** 以收集所有值,同时忽略 Generator 返回的键。

示例 #5 使用 yield fromiterator_to_array()

<?php
function inner() {
yield
1; // 键 0
yield 2; // 键 1
yield 3; // 键 2
}
function
gen() {
yield
0; // 键 0
yield from inner(); // 键 0-2
yield 4; // 键 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 [
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

示例 #7 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();
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
添加注释

用户贡献的注释 10 注释

Adil lhan (adilmedya at gmail dot com)
11 年前
例如,带有斐波那契数列的 yield 关键字

function getFibonacci()
{
$i = 0;
$k = 1; // 斐波那契数列的第一个值
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; // 防止无限循环
}
}
info at boukeversteegh dot nl
9 年前
[此评论替换了我之前的评论]

您可以使用生成器来对列表进行延迟加载。您只计算实际使用的项目。但是,当您想要加载更多项目时,如何缓存已经加载的项目呢?

以下是使用生成器进行缓存的延迟加载的方法

<?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";
}
?>
Hayley Watson
8 年前
如果您出于某种奇怪的原因需要一个不产生任何值的生成器,那么空函数不起作用;该函数需要一个 yield 语句才能被识别为生成器。

<?php

function gndn()
{
}

foreach(
gndn() as $it)
{
echo
'FNORD';
}

?>

但是,即使 yield 在语法上是存在的,它也足够了,即使它无法访问

<?php

function gndn()
{
if(
false) { yield; }
}

foreach(
gndn() as $it)
{
echo
'FNORD';
}

?>
zilvinas at kuusas dot lt
8 年前
不要直接调用生成器函数,这样不起作用。

<?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) {
// ...
}
?>
Harun Yasar harunyasar at mail dot com
9 年前
这是一个简单的斐波那契数列生成器。

<?php
function fibonacci($item) {
$a = 0;
$b = 1;
for (
$i = 0; $i < $item; $i++) {
yield
$a;
$a = $b - $a;
$b = $a + $b;
}
}

# 获取前十个斐波那契数
$fibo = fibonacci(10);
foreach (
$fibo as $value) {
echo
"$value\n";
}
?>
christophe dot maymard at gmail dot com
9 年前
<?php
// 使用生成器实现 IteratorAggregate 接口的示例

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;
}
}
}

// 初始化集合
$collection = new ValueCollection();
$collection
->addValue('A string')
->
addValue(new stdClass())
->
addValue(NULL);

foreach (
$collection as $item) {
var_dump($item);
}
Shumeyko Dmitriy
10 年前
这是一个使用生成器和递归的简单示例。使用的 PHP 版本是 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) { // 读取目录中的所有文件
if (($FileName != CURRENT_DIR) && ($FileName != PARENT_DIR)) {
$FullName = $DirName.$FileName;
yield
$FullName;
if(
is_dir($FullName)) { // 包含子文件和子目录
$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]
harl at gmail dot com
14 天前
如果你混合使用带键值和不带键值的 yield,结果与向数组添加带键值或不带键值的元素相同。

<?php
function gen() {
yield
'a';
yield
4 => 'b';
yield
'c';
}

$t = iterator_to_array(gen());
var_dump($t);
?>

结果是一个数组 [0 => 'a', 4 => 'b', 5 => 'c'],就像你写了

<?php
$t
= [];
$t[] = 'a';
$t[4] = 'b';
$t[] = 'c';
var_dump($t);
?>

分配给 'c' 的键值从之前的数字索引递增。
christianggimenez at gmail dot com
5 年前
从 1 到 100 的数字的模运算结果列表。

<?php

function list_of_modulo(){

for(
$i = 1; $i <= 100; $i++){

if((
$i % 2) == 0){
yield
$i;
}
}
}

$modulos = list_of_modulo();

foreach(
$modulos as $value){

echo
"$value\n";
}

?>
denshadewillspam at HOTMAIL dot com
10 年前
请注意,你不能在生成器上使用 count()。

/**
* @return integer[]
*/
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;
To Top