什么是生成器?听着高大上的名字,感觉像是在创造什么的功能。其实生成器就是一个迭代器,用于迭代。它提供了一种更简单的方法来实现简单的对象迭代,与定义一个类来实现Iterator接口的方式相比,性能开销和复杂度大大降低。说了半天,直接看代码更直观。functiontest1(){for($i=0;$i<3;$i++){yield$i+1;}产量1000;yield1001;}foreach(test1()as$t){echo$t,PHP_EOL;}//1//2//3//1000//1001就是这样一段简单的代码。首先,生成器必须在方法中,并使用yield关键字;第二,每一份收益都可以看作是一种回报;最后,在外层循环中,一个循环获取yield的返回值。本例循环三次返回1、2、3这三个数,然后在循环外写两行yield,分别输出1000和1001。因此,外部foreach总共循环了五次输出。很神奇,明明是方法,为什么可以循环而且返回循环体还是很奇怪的格式。我们直接打印这个test()方法,看看打印的是什么://是一个generator对象var_dump(test1());//GeneratorObject//(//)使用yield返回内容后,返回的是一个生成器对象。这个对象称为生成器对象,不能直接new实例化,只能通过生成器函数返回。该类包含current()、key()等方法,最重要的类实现了Iterator接口,因此是一个特殊的迭代器类。GeneratorimplementsIterator{/*method*/publiccurrent(void):mixedpublickey(void):mixedpublicnext(void):voidpublicrewind(void):voidpublicsend(mixed$value):mixedpublicthrow(异常$exception):voidpublicvalid(void):boolpublic__wakeup(void):void}生成器有什么用?搞了半天,不就是个迭代器吗?怎么这么麻烦,直接用迭代器或者直接在方法中返回一个数组不好吗?没错,一般情况下确实没那么麻烦,但是如果数据量特别大,这个生成器就可以发挥它强大的威力了。生成器最强大的部分是它不需要数组或任何数据结构来保存这一系列数据。当代码执行到yield时,每次迭代都会动态返回。因此,生成器可以节省大量内存。//内存使用测试$start_time=microtime(true);函数test2($clear=false){$arr=[];如果($clear){$arr=null;返回;}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"内存(byte):",memory_get_usage(true),PHP_EOL;//time:0.0517//memory(byte):2097152上面的代码只是简单的获取了1000000次循环后的结果,但是也可以直观的看出。使用生成器的版本只消耗2M内存,而没有生成器的版本消耗内存35M,相差10倍以上,而且数量越大,差异越明显。因此,生成器被认为是PHP中最被低估的特性之一。生成器的应用接下来,我们来看一下生成器的一些基本应用方法。返回空值,中断生成器当然也可以返回空值,直接yield;没有任何值可以返回空值。而在方法中直接使用return也可以用来打断generator的继续执行。在下面的代码中,当\$i=4;时,我们返回一个空值,即我们不会输出5(因为我们返回了$i+1)。然后使用返回;when$i==7中断generator的继续执行,即循环只会输出到7然后结束。//返回null并中断functiontest4(){for($i=0;$i<10;$i++){if($i==4){yield;//返回空值}if($i==7){return;//中断生成器执行}yield$i+1;}}foreach(test4()as$t){echo$t,PHP_EOL;}//1//2//3//4//5//6//7不要惊讶返回键值pairs,生成器真的可以为foreach返回键值对形式的可遍历对象,语法非常好记:yieldkey=>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方法生成一个值传入设备。传入的值将作为生成器当前收益的返回值。那么我们可以根据这个值做一些判断,比如根据外部条件中断生成器的执行。functiontest6(){for($i=0;$i<10;$i++){//正常获取循环值,当从外部发送值时,yield从外部获取值$data=(yield$i+1);如果($data=='stop'){返回;}}}$t6=test6();foreach($t6as$t){if($t==3){$t6->send('stop');}echo$t,PHP_EOL;}//1//2//3上面的代码可能看不懂,但只要记住注释中的行话即可(一般是获取循环值,当值从外部发送过来时,yield得到的是来自外部的值)。另外,获取yield值的变量必须用括号括起来。yieldfrom语法yieldfrom语法实际上是指从另一个可迭代对象中逐个获取数据,形成生成器返回。直接看代码。函数test7(){从[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()方法中,我们使用yieldfrom从普通数组中获取数据,iterator对象,和另一个生成器并将它们作为当前生成器的内容返回。LittleSurpriseGenerator可以使用计数来获取数量吗?抱歉,生成器无法使用计数来获取其编号。$c=计数(测试1());//警告:count():参数必须是数组或实现Countable的对象//echo$c,PHP_EOL;使用count获取生成器数量会直接报Warning警告。由于计数的性质,直接输出将始终显示1(强制转换为数组将显示1)。使用生成器获取斐波那契数列//使用生成器生成斐波那契数列functionfibonacci($item){$a=0;$b=1;对于($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。phphttps://www.php.net/manual/zh/class.generator.php============各媒体平台均可搜索【硬核项目经理】
