我最近无意中发现了这段代码:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

我以前从未见过这个yield关键字。试着运行我得到的代码

解析错误:语法错误,意外的T_VARIABLE在x行

那么yield关键字是什么呢?它是有效的PHP吗?如果是,我该怎么用呢?


当前回答

简单的例子

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

输出

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#

先进的例子

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $k => $v){
    if($k === 5)
        break;
    echo $k.'=>'.$v.',';
}
echo '#end main#';
?>

输出

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#

其他回答

简单的例子

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

输出

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#

先进的例子

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $k => $v){
    if($k === 5)
        break;
    echo $k.'=>'.$v.',';
}
echo '#end main#';
?>

输出

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#

使用yield,您可以很容易地描述单个函数中多个任务之间的断点。就是这样,没什么特别的。

$closure = function ($injected1, $injected2, ...){
    $returned = array();
    //task1 on $injected1
    $returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
    //task2 on $injected2
    $returned[] = $returned2;
    //...
    return $returned;
};
$returned = $closure($injected1, $injected2, ...);

如果task1和task2高度相关,但你需要在它们之间设置一个断点来做其他事情:

处理数据库行之间的空闲内存 运行对下一个任务提供依赖关系的其他任务,但通过理解当前代码,这些任务是不相关的 执行异步调用并等待结果 等等……

生成器是最好的解决方案,因为你不需要把你的代码分割成许多闭包,或者与其他代码混合,或者使用回调等等……您只需使用yield来添加一个断点,如果准备好了,可以从该断点继续。

添加没有生成器的断点:

$closure1 = function ($injected1){
    //task1 on $injected1
    return $returned1;
};
$closure2 = function ($injected2){
    //task2 on $injected2
    return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...

使用生成器添加断点

$closure = function (){
    $injected1 = yield;
    //task1 on $injected1
    $injected2 = (yield($returned1));
    //task2 on $injected2
    $injected3 = (yield($returned2));
    //...
    yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);

注意:使用生成器很容易犯错误,所以在实现它们之前一定要编写单元测试! 注2:在无限循环中使用生成器就像写一个无限长的闭包……

一个值得在此讨论的有趣方面是参照让步。每次我们需要改变一个形参以使其反映在函数外部时,我们必须通过引用传递这个形参。要将此应用于生成器,只需在生成器的名称和迭代中使用的变量前加上&:

 <?php 
 /**
 * Yields by reference.
 * @param int $from
 */
function &counter($from) {
    while ($from > 0) {
        yield $from;
    }
}

foreach (counter(100) as &$value) {
    $value--;
    echo $value . '...';
}

// Output: 99...98...97...96...95...

上面的例子展示了在foreach循环中改变迭代值如何改变生成器中的$from变量。这是因为由于生成器名称前有&号,$from是通过引用产生的。正因为如此,foreach循环中的$value变量是生成器函数中$from变量的引用。

下面的代码说明了如何使用生成器在完成之前返回一个结果,而不像传统的非生成器方法在完整迭代之后返回一个完整的数组。使用下面的生成器,值在准备就绪时返回,不需要等待数组被完全填充:

<?php 

function sleepiterate($length) {
    for ($i=0; $i < $length; $i++) {
        sleep(2);
        yield $i;
    }
}

foreach (sleepiterate(5) as $i) {
    echo $i, PHP_EOL;
}

没有一个答案给出了使用由非数字成员填充的大型数组的具体示例。下面是一个例子,在一个大的.txt文件(在我的用例中是262MB)上使用由explosion()生成的数组:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

输出结果是:

Starting memory usage: 415160
Final memory usage: 270948256

现在将其与类似的脚本进行比较,使用yield关键字:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

这个脚本的输出是:

Starting memory usage: 415152
Final memory usage: 415616

显然,内存使用节省相当大(ΔMemoryUsage -----> ~270.5 MB在第一个示例中,~450B在第二个示例中)。