在PHP5.5.0版本中,新增了生成器(Generators)特性,用于简化实现迭代器接口(Iterator)创建简单的迭代器的复杂性。
通过生成器,我们可以轻松的使用foreach
迭代一系列的数据,而不需要事先在内存中构建要被迭代的对象,大大减少了内存开销。
当生成器函数被调用的时候,它会返回一个可迭代的对象,当对该对象进行迭代的时候,PHP将会在需要的时候调用生成器函数,并且在生成器使用新增的关键字yield
产生一个新的值的时候,保存迭代器内部的状态。迭代器没有新的值需要产生的时候,生成器函数就可以直接退出,外部函数继续执行。
注意,在生成器函数中,不能使用
return
语句返回值,使用return
返回值的话会产生编译器错误。但是,使用空的return
是可以的,它会使迭代器终止。
生成器函数与普通函数一样的,唯一的区别函数内使用了yield
关键字。yield
语句可以说是生成器函数的核心,简单来说,yield
就像return
语句一样,区别是return
语句返回后函数就结束了,而使用yield
返回后,只是暂停了函数的执行,转到外部函数继续执行,下次调用生成器函数的时候,继续执行生成器函数内部的代码。
一个简单的例子 - 生成器版本的range函数
一个简单的例子是使用foreach
迭代函数range
的返回值,如果调用的是range(0, 1000000)
的话,将会消耗超过100M的内存。而使用生成器的话,可能只需要消耗1KB内存都不到。
<?php
function xrange($start, $end) {
if ($start > $end) {
throw new RuntimeException("起始值不能大于截止值");
}
for ($i = $start; $i <= $end; $i += 1) {
// 使用yield关键字,每次到这里函数都会返回$i的值,并且控制权交给外部函数继续执行
yield $i;
}
}
foreach (xrange(1, 9) as $number) {
echo "$number ";
}
上面的例子输出如下:
上述例子中,我们创建了一个名为xrange
的函数,函数中使用yield
不断产生返回值,而调用xrange(1, 9)
将会创建一个生成器对象。我们可以修改foreach
这一行打印出xrange
对象看看
...
$xrange_res = xrange(1, 9);
var_dump($xrange_res);
foreach( $xrange_res as $number){
...
输出
可以看出,执行xrange(1, 9)
的时候确实是返回了一个Generator
对象。
使用Generator对象的send方法
在上面的例子中,我们使用yield
语句的时候都是作为单独的一行语句执行的,也就是yield
语句产生结果给外部,那么在迭代过程中有没有办法从生成器函数外部获取值呢?
办法总是有的,因为调用生成器函数后返回的是一个Generator
对象,因此我们可以通过调用该对象的send
方法从外部给生成器函数传递一个值,在调用send
方法之后,yield
会收到send
函数发送的值。
<?php
function gen() {
$ret = (yield 'yield1');
var_dump("-->" . $ret);
$ret = (yield 'yield2');
var_dump("-->" . $ret);
}
$gen = gen();
var_dump($gen->current());
var_dump($gen->send('ret1'));
var_dump($gen->send('ret2'));
输出:
这里我们首先创建了名为gen
的生成器对象,然后打印$gen->current()
方法的返回值,该返回值就是迭代器第一次迭代时产生的当前值,因此输出了yield1
。
接下来我们调用了$gen->send('ret')
方法,这时,生成器内第一个yield
语句返回该方法传递的值ret1
,因此输出了$ret
的值为ret1
。
接着由于生成器内部执行到了第三条语句$ret = (yield 'yield2')
,因此外部的第二个var_dump
输出了yield2
。最后调用$gen->send('ret2')
与第一次类似,不过这次生成器内部调用yield
之后已经没有yield
了,因此返回的是NULL
。
注意,这里的
$ret = (yield 'yield2')
语句中,使用括号包含了yield 'yield2'
语句,这里是必须的,如果在表达式上下文中使用yield
,必须将yield
放在括号内,否则会报错。
返回关联数组
前面的例子中,我们使用yield
关键字返回的总是单个值,实际上PHP也对返回关联数组提供了支持,基本语法:
yield key => val
使用该语法格式可以在foreach的时候,返回与遍历管理数组相同的结果。
<?php
function gen2() {
$array = [
'username' => 'mylxsw',
'site' => 'http://aicode.cc'
];
foreach ($array as $key => $val) {
yield $key => $val;
}
}
foreach(gen2() as $key => $val) {
var_dump($key . ' : ' . $val);
}
输出:
使用引用
我们还可以让生成器以引用的方式返回数据,这样就可以在生成器外部直接修改生成器内部数据的值。
<?php
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
上述例子中,需要注意的是,生成器函数的定义和遍历的时候使用了&$number
。
最后,生成器与自定义的迭代器对象是不完全相同的,生成器一旦开始迭代,就不能再rewind
了,只能一直向前迭代,直到迭代完成。如果希望多次迭代一个生成器对象的话,可以多次调用生成器函数创建新的生成器对象或者是使用clone关键字。
参考: