php中的生成器函数 yield

生成器函数和普通函数很类似,不同的是生成器函数有个关键词 yield 。如果一个函数中使用了 yield ,这个函数就是一个生成器函数。

生成器函数返回的是一个可以被遍历的对象

意思是返回的是一个可以被遍历的对象。每次遍历可以获取一个yield 的返回值, 一个生成器函数可以有多个 yield 。

yield 一个例子

function test_yield(){
    echo "c1" . PHP_EOL;
    yield 1;
    yield 2;
    echo "c2" .PHP_EOL;
    yield 3;
    yield 4;
}


$result = test_yield(); // 这里调用上面我们创建的函数
echo  "[" .  date("H:i:s")  ."]" . PHP_EOL;
foreach($result as $value){
    sleep(1);//这里停顿1秒,我们后续有用
    echo $value . "[" .  date("H:i:s")  ."]" . PHP_EOL;
}

输出的值

[05:58:30]
c1
1[05:58:31]
2[05:58:32]
c2
3[05:58:33]
4[05:58:34]

可以看出每次遍历输出一个yield 的生成值。在 foreach 中一共输出了 4 个。

https://www.php.net/manual/zh/language.generators.overview.php

生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。一个简单的例子就是使用生成器来重新实现 range() 函数。 标准的 range() 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组, 结果就是会产生多个很大的数组。 比如,调用 range(0, 1000000) 将导致内存占用超过 100 MB。做为一种替代方法, 我们可以实现一个 xrange() 生成器, 只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到1K字节的内存。

https://www.php.net/manual/zh/language.generators.syntax.php

生成器函数看起来像普通函数——不同的是普通函数返回一个值,而生成器可以 yield 生成多个想要的值。 任何包含 yield 的函数都是一个生成器函数。当一个生成器被调用的时候,它返回一个可以被遍历的对象.当你遍历这个对象的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用对象的遍历方法,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。一旦不再需要产生更多的值,生成器可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。

示例: 使用更少的内存

function test($n = 10) {
    $arr = [];

    for($i = 0; $i < $n ; $i++) {
        $arr[$i] = $i;
    }

    return $arr;
}

function test2($n = 10) {
    for($i = 0; $i < $n ; $i++) {
        yield $i;
    }
}


/*

 * 会导致内存不足
foreach( test(PHP_INT_MAX) as $ans ) {
    echo $ans;
}
 */

foreach( test2(PHP_INT_MAX) as $ans ) {
    echo $ans;
}

读取文件的示例

function readlog ($filename) {
    $file = fopen($filename, 'rb');

    echo "-- start " . PHP_EOL;
    # feof 错误或者到文件结尾 返回 true
    while (feof($file) === false ) {
        yield fgets($file);
    }

    echo "-- start " . PHP_EOL;
    fclose($file);
}


foreach ( readlog("web.log") as $line => $value ) {
    echo " $line - " . $value;
    if($line >= 2 ) {
        // !!! 没有执行完,会导致 echo "-- start " . PHP_EOL; fclose($file); 没有运行
        break;
    }
}