PHP 生成器 Generators

更新时间:2023-10-29 14:58

使用生成器类似于编写函数,但不使用 return关键字,您使用 yield陈述。 yield可以在同一函数中多次使用,并从上到下按顺序读取(作为 值序列 返回)。

通过使用生成器,您正在调用迭代器类,这意味着您正在使用生成器对象。 正如我们稍后将看到的,生成器对象允许您访问许多有用的方法,并且还可以跟踪生成器在迭代中的位置,这在迭代大型或无限数组时会产生影响。

让我们看一下生成器的一个非常基本的语法示例:

function gen_one_to_three() {
    for ( $i = 1; $i <= 3; $i++ ) {
        yield $i;
        yield 'abc';
    }
}

$generator = gen_one_to_three();

foreach ( $generator as $value ) {
    echo "$value\n";
}

// Above will output 1 abc 2 abc 3 abc

在这里,每一次 gen_one_to_three()迭代,它产生两个值。 这些是按顺序执行的,首先 yield $i第二个 yield “abc"。 因为我们的 for 循环计数到 3,所以我们得到结果 yield输出三倍。

让我们看另一个例子:

function  counter($num) {
    for ($i = 1; $i < $num; $i++) {
        yield $i;
    }
}

foreach (counter(PHP_INT_MAX) as $count) {
    echo "Yield: " . $count . PHP_EOL;
}

我们已将计数器设置为计数 PHP_INT_MAX,或者换句话说,要求计数器计数到 9223372036854775808(假设它在 64 位系统上运行)。 传统上,尝试创建数组并迭代如此大的数字将会失败,或者您必须显着增加 PHP 可用的内存分配。 但是,运行上面的代码将会起作用,它将逐行输出每个数字,始终从上次产生的位置继续,直到达到 PHP_INT_MAX。 生成器如何知道它最后产生的位置? 这是可行的,因为生成器创建了一个迭代器对象,并且可以在内部跟踪其当前状态,使用的内存比尝试将整个数组放入内存时要少得多。

这会带来各种可能性,从迭代大型数组时提高内存使用率到在应用程序中创建并发性。

生成器对象和协程

正如我们所见,生成器实现了 PHP 迭代器类。 这意味着,生成器本身就是对象,具有可以调用和使用的方法。

在上面的例子中 $generator = gen_one_to_three()中,我们看到了将生成器对象分配给变量的常见用例。 通过这样做,我们创建了 PHP 内部生成器类的实例。

所以与 $generator对象现在实例化为变量,我们现在可以访问对象的方法,例如 $generator->current()获取当前收益率值或 $generator->send()它允许您将值作为yield 表达式传递 生成器。

查看 PHP 手册 中的这个示例,使用 send()方法:

function printer() {
    echo "I'm printer!". PHP_EOL;

    while (true) {
        $string = yield;
        echo $string . PHP_EOL;
    }
}

$printer = printer();

$printer->send('Hello world!');
$printer->send('Bye world!');

执行生成器函数时,它首先运行 echo 语句,然后按顺序处理 Yield 表达式 send()方法。

能够将值发送回生成器是一个强大的概念,允许协程和多任务处理。 Nikita Popov 提供了一篇关于该主题的优秀博客文章,题为 使用协程进行协作多任务(在 PHP 中!)

最后,我想看看 getReturn()返回一个值 PHP 7 中引入的生成器方法。这允许您在生成器运行 ,对于 PHP 中的模块化编程特别有用。 PHP 文档 getReturn提供以下示例:

$gen = (function() {
    yield 1;
    yield 2;
    // 3 is only returned after the generator has finished
    return 3;

})();

foreach ($gen as $val) {
    echo $val, PHP_EOL;
}

echo $gen->getReturn(), PHP_EOL;

// Outputs 1 2 3

这里,在返回两个yield(1和2)并且生成器完成运行后,生成器返回数字3。 呼唤 getReturn()当生成器尚未返回或关闭时,将抛出异常。 在调用之前检查生成器是否已完成的一种方法 getReturn(),是通过使用 valid()方法。 它会返回 false如果发电机已关闭。

有关使用生成器时可以调用的所有方法的更多一般信息,请访问有关 生成器 的 PHP 手册。

发电机委托

我想介绍的生成器的最后一个功能是关于yield 语句的使用。 PHP 7 为生成器引入了许多新功能,其中之一是 生成器委托 ,它允许您使用关键字将值从一个生成器生成到另一个生成器 yield from.

中的委托示例 让我们看一下文档
 

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

// This yields 1 2 3 4 5 6 7 8 9 10

在这里我们看到 count_to_ten()被调用的函数。 然后从上到下逐个执行yield 语句。 当每个函数被调用时,它们都会返回值和其他带有自己的yield 语句的函数。

通过这种方式,生成器能够委托给数组、对象或函数。 一旦该对象或函数运行,它将委托回原始函数,在我们的例子中, count_to_ten(). count_to_ten()将继续执行,直到没有更多的yield 语句可供执行。

这种来回通信形成了非阻塞并发框架使用的协程的基础知识。

结论

我只涉及了有关生成器的一些基本概念及其带来的优势。 它们对于现代 PHP 编程来说确实是基础且有用。 无论您是使用它们来节省内存使用量还是编写更高级的编程结构(例如协程和异步 PHP),都值得花一些时间学习它们提供的功能。