PHP 生成器 Generators
使用生成器类似于编写函数,但不使用 return
关键字,您使用 yield
陈述。 yield
可以在同一函数中多次使用,并从上到下按顺序读取(作为 值序列 返回)。
通过使用生成器,您正在调用迭代器类,这意味着您正在使用生成器对象。 正如我们稍后将看到的,生成器对象允许您访问许多有用的方法,并且还可以跟踪生成器在迭代中的位置,这在迭代大型或无限数组时会产生影响。
让我们看一下生成器的一个非常基本的语法示例:
在这里,每一次 gen_one_to_three()
迭代,它产生两个值。 这些是按顺序执行的,首先 yield $i
第二个 yield “abc"
。 因为我们的 for 循环计数到 3,所以我们得到结果 yield
输出三倍。
让我们看另一个例子:
我们已将计数器设置为计数 PHP_INT_MAX
,或者换句话说,要求计数器计数到 9223372036854775808(假设它在 64 位系统上运行)。 传统上,尝试创建数组并迭代如此大的数字将会失败,或者您必须显着增加 PHP 可用的内存分配。 但是,运行上面的代码将会起作用,它将逐行输出每个数字,始终从上次产生的位置继续,直到达到 PHP_INT_MAX
。 生成器如何知道它最后产生的位置? 这是可行的,因为生成器创建了一个迭代器对象,并且可以在内部跟踪其当前状态,使用的内存比尝试将整个数组放入内存时要少得多。
这会带来各种可能性,从迭代大型数组时提高内存使用率到在应用程序中创建并发性。
生成器对象和协程
正如我们所见,生成器实现了 PHP 迭代器类。 这意味着,生成器本身就是对象,具有可以调用和使用的方法。
在上面的例子中 $generator = gen_one_to_three()
中,我们看到了将生成器对象分配给变量的常见用例。 通过这样做,我们创建了 PHP 内部生成器类的实例。
所以与 $generator
对象现在实例化为变量,我们现在可以访问对象的方法,例如 $generator->current()
获取当前收益率值或 $generator->send()
它允许您将值作为yield 表达式传递 回 生成器。
查看 PHP 手册 中的这个示例,使用 send()
方法:
执行生成器函数时,它首先运行 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),都值得花一些时间学习它们提供的功能。