说了半天不如直接看看代码更直不雅观。
functiontest1(){for($i=0;$i<3;$i++){yield$i+1;}yield1000;yield1001;}foreach(test1()as$t){echo$t,PHP_EOL;}//1//2//3//1000//1001
便是这么大略的一段代码。首先,天生器必须在方法中并利用 yield 关键字;其次,每一个 yield 可以看作是一次 return ;末了,外部循环时,一次循环取一个 yield 的返回值。在这个例子,循环三次返回了1、2、3这三个数字。然后在循环外部又写了两行 yield 分别输出了1000和1001。因此,外部的 foreach 一共循环输出了五次。
很神奇吧,明明是一个方法,为什么能够循环它而且还是很奇怪的一种返回循环体的格式。我们直接打印这个 test() 方法看看打印的是什么:

//是一个天生器工具var_dump(test1());//GeneratorObject//(//)
当利用了 yield 进行内容返回后,返回的是一个 Generator 工具。这个工具就叫作天生器工具,它不能直接被 new 实例化,只能通过天生器函数这种办法返回。这个类包含 current() 、 key() 等方法,而且最紧张的这个类实现了 Iterator 接口,以是,它便是一个分外的迭代器类。
GeneratorimplementsIterator{/方法/publiccurrent(void):mixedpublickey(void):mixedpublicnext(void):voidpublicrewind(void):voidpublicsend(mixed$value):mixedpublicthrow(Exception$exception):voidpublicvalid(void):boolpublic__wakeup(void):void}
天生器有什么用?
搞了半天不便是个迭代器嘛?搞这么麻烦干嘛,直接用迭代器或者在方法中直接返回一个数组不就好了吗?没错,正常情形下真的没有这么麻烦,但是如果是在数据量特殊大的情形下,这个天生器就能发挥它的强大威力了。天生器最最强大的部分就在于,它不须要一个数组或者任何的数据构造来保存这一系列数据。每次迭代都是代码实行到 yield 时动态返回的。因此,天生器能够极大的节约内存。
//内存占用测试$start_time=microtime(true);functiontest2($clear=false){$arr=[];if($clear){$arr=null;return;}for($i=0;$i<1000000;$i++){$arr[]=$i+1;}return$arr;}$array=test2();foreach($arrayas$val){}$end_time=microtime(true);echo"time:",bcsub($end_time,$start_time,4),PHP_EOL;echo"memory(byte):",memory_get_usage(true),PHP_EOL;//time:0.0513//memory(byte):35655680$start_time=microtime(true);functiontest3(){for($i=0;$i<1000000;$i++){yield$i+1;}}$array=test3();foreach($arrayas$val){}$end_time=microtime(true);echo"time:",bcsub($end_time,$start_time,4),PHP_EOL;echo"memory(byte):",memory_get_usage(true),PHP_EOL;//time:0.0517//memory(byte):2097152
上述代码只是大略的进行 1000000 个循环后获取结果,不过也可以直不雅观地看出。利用天生器的版本仅仅花费了 2M 的内存,而未利用天生器的版本则花费了 35M 的内存,直接已经10多倍的差距了,而且越大的量差距超明显。因此,有大神将天生器说成是PHP中最被低估了的一个特性。
天生器的运用接下来我们来看看天生器的一些基本的运用办法。
返回空值以及中断天生器当然也可以返回空值,直接 yield; 不带任何值就可以返回一个空值了。而在方法中直策应用 return; 也可以用来中断天生器的连续实行。下面的代码我们在 \$i = 4; 的时候返回的是个空值,也便是不会输出 5 (由于我们返回的是 $i + 1 )。然后在 $i == 7 的时候利用 return; 中断天生器的连续实行,也便是循环最多只会输出到 7 就结束了。
//返回空值以及中断functiontest4(){for($i=0;$i<10;$i++){if($i==4){yield;//返回null值}if($i==7){return;//中断天生器实行}yield$i+1;}}foreach(test4()as$t){echo$t,PHP_EOL;}//1//2//3//4//5//6//7
返回键值对形式
不要惊异,天生器真的是可以返回键值对形式的可遍历工具供 foreach 利用的,而且语法非常好记: yield key => value; 是不是和数组项的定义形式千篇一律,非常直不雅观好理解。
functiontest5(){for($i=0;$i<10;$i++){yield'key.'.$i=>$i+1;}}foreach(test5()as$k=>$t){echo$k.':'.$t,PHP_EOL;}//key.0:1//key.1:2//key.2:3//key.3:4//key.4:5//key.5:6//key.6:7//key.7:8//key.8:9//key.9:10
外部通报数据
我们可以通过 Generator::send 方法来向天生器中传入一个值。传入的这个值将会被当做天生器当前 yield 的返回值。然后我们根据这个值可以做一些判断,比如根据外部条件中断天生器的实行。
functiontest6(){for($i=0;$i<10;$i++){//正常获取循环值,当外部send过来值后,yield获取到的便是外部传来的值了$data=(yield$i+1);if($data=='stop'){return;}}}$t6=test6();foreach($t6as$t){if($t==3){$t6->send('stop');}echo$t,PHP_EOL;}//1//2//3
上述代码理解起来可能比较绕,但是把稳记住注释的那行话就行了(正常获取循环值,当外部send过来值后,yield获取到的便是外部传来的值了)。其余,变量获取 yield 的值,必须要用括号括起来。
yield from 语法yield from 语法实在便是指的从另一个可迭代工具中一个一个的获取数据并形成天生器返回。直接看代码。
functiontest7(){yieldfrom[1,2,3,4];yieldfromnewArrayIterator([5,6]);yieldfromtest1();}foreach(test7()as$t){echo'test7:',$t,PHP_EOL;}//test7:1//test7:2//test7:3//test7:4//test7:5//test7:6//test7:1//test7:2//test7:3//test7:1000
在 test7() 方法中,我们利用 yield from 分别从普通数组、迭代器工具、另一个天生器中获取数据并做为当前天生器的内容进行返回。
小惊喜天生器可以用count获取数量吗?抱歉,天生器是不能用count来获取它的数量的。
$c=count(test1());//Warning:count():ParametermustbeanarrayoranobjectthatimplementsCountable//echo$c,PHP_EOL;
利用 count 来获取天生器的数量将直接报 Warning 警告。直接输出将会一贯显示是 1 ,由于 count 的特性(逼迫转换成数组都会显示 1 )。
利用生产器来获取斐波那契数列//利用天生器天生斐波那契数列functionfibonacci($item){$a=0;$b=1;for($i=0;$i<$item;$i++){yield$a;$a=$b-$a;$b=$a+$b;}}$fibo=fibonacci(10);foreach($fiboas$value){echo"$value\n";}
这段代码就不多阐明了,非常直不雅观的一段代码了。
总结天生器绝对是PHP中的一个隐蔽的宝藏,不仅是对付内存节约来说,而且语法实在也非常的简洁明了。我们不须要在方法内部再多定义一个数组去存储返回值,直接 yield 一项一项的返回就可以了。在实际的项目中完备值得考试测验一把,但是考试测验完了别忘了和小伙伴们分享,大部分人可能真的没有打仗过这个特性哦!
!
测试代码: https://github.com/zhangyue0503/dev-blog/blob/master/php/202002/source/%E5%AD%A6%E4%B9%A0PHP%E7%94%9F%E6%88%90%E5%99%A8%E7%9A%84%E4%BD%BF%E7%94%A8.php
参考文档: https://www.php.net/manual/zh/language.generators.overview.php
https://www.php.net/manual/zh/class.generator.php