yield 协程
1.初识Generator
Generator , 一种可以返回迭代器的生成器,当程序运行到yield的时候,当前程序就唤起协程记录上下文,然后主函数继续操作,当需要操作的时候,在通过迭代器的next重新调起
function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; } } foreach (xrange(1, 1000) as $num) { echo $num, "\n"; } /* * 1 * 2 * ... * 1000 */
如果了解过迭代器的朋友,就可以通过上面这一段代码看出Generators的运行流程
Generators::rewind() 重置迭代器 Generators::valid() 检查迭代器是否被关闭 Generators::current() 返回当前产生的值 Generators::next() 生成器继续执行 Generators::valid() Generators::current() Generators::next() ... Generators::valid() 直到返回 false 迭代结束
2.Generator应用
很多不了解的朋友看完可能会表示这有什么用呢?
举个栗子:
比如从数据库取出数亿条数据,这个时候要求用一次请求加响应返回所有值该怎么办呢?获取所有值,然后输出,这样肯定不行,因为会造成PHP内存溢出的,因为数据量太大了。如果这时候用yield就可以将数据分段获取,理论上这样是可以取出无限的数据的。
一般的获取方式 :
数据库连接..... $sql = "select * from `user` limit 0,500000000"; $stat = $pdo->query($sql); $data = $stat->fetchAll(); //mysql buffered query遍历巨大的查询结果导致的内存溢出 var_dump($data);
yield获取方式:
数据库连接..... function get(){ $sql = "select * from `user` limit 0,500000000"; $stat = $pdo->query($sql); while ($row = $stat->fetch()) { yield $row; } } foreach (get() as $row) { var_dump($row); }
3.深入了解Generator
看完这些之后可能有朋友又要问了,这跟标题的中间件有什么关系吗
是的上面说的这些确实跟中间件没关系,只是单纯的介绍yield,但是你以为yield只能这样玩吗?
在我查阅了http://php.net/manual/zh/class.generator.php 内的Generators资料之后我发现了一个函数
Generator::send
官方的介绍 :
向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。
如果当这个方法被调用时,生成器不在 yield 表达式,那么在传入值之前,它会先运行到第一个 yield 表达式。As such it is not necessary to “prime” PHP generators with a Generator::next() call (like it is done in Python).
这代表了什么,这代表了我们可以使用yield进行双向通信
再举个栗子
$ben = call_user_func(function (){ $hello = (yield 'my name is ben ,what\'s your name'.PHP_EOL); echo $hello; }); $sayHello = $ben->current(); echo $sayHello; $ben->send('hi ben ,my name is alex'); /* * output * * my name is ben ,what's your name * hi ben ,my name is alex */
这样ben跟alex他们两个就实现了一次相互问好,在这个例子中我们可以发现,yield跟以往的return不同,它不仅可以返回数据,还可以获取外部返回的数据
而且不仅仅能够send,PHP还提供了一个throw,允许我们返回一个异常给Generator
$Generatorg = call_user_func(function(){ $hello = (yield '[yield] say hello'.PHP_EOL); echo $hello.PHP_EOL; try{ $jump = (yield '[yield] I jump,you jump'.PHP_EOL); }catch(Exception $e){ echo '[Exception]'.$e->getMessage().PHP_EOL; } }); $hello = $Generatorg->current(); echo $hello; $jump = $Generatorg->send('[main] say hello'); echo $jump; $Generatorg->throw(new Exception('[main] No,I can\'t jump')); /* * output * * [yield] say hello * [main] say hello * [yield] I jump,you jump * [Exception][main] No,I can't jump */
4.中间件
在了解了yield那么多语法之后,就要开始说说我们的主题了,中间件,具体思路是以迭代器的方式调用函数,先current执行第一个yield之前的代码,再用send或者next执行下一段代码,下面就是简单的实现
function middleware($handlers,$arguments = []){ //函数栈 $stack = []; $result = null; foreach ($handlers as $handler) { // 每次循环之前重置,只能保存最后一个处理程序的返回值 $result = null; $generator = call_user_func_array($handler, $arguments); if ($generator instanceof \Generator) { //将协程函数入栈,为重入做准备 $stack[] = $generator; //获取协程返回参数 $yieldValue = $generator->current(); //检查是否重入函数栈 if ($yieldValue === false) { break; } } elseif ($generator !== null) { //重入协程参数 $result = $generator; } } $return = ($result !== null); //将协程函数出栈 while ($generator = array_pop($stack)) { if ($return) { $generator->send($result); } else { $generator->next(); } } } $abc = function(){ echo "this is abc start \n"; yield; echo "this is abc end \n"; }; $qwe = function (){ echo "this is qwe start \n"; $a = yield; echo $a."\n"; echo "this is qwe end \n"; }; $one = function (){ return 1; }; middleware([$abc,$qwe,$one]); /* * output * * this is abc start * this is qwe start * 1 * this is qwe end * this is abc end */
通过middleware()方法我们就实现了一个这样的效果
(begin) ----------------> function() -----------------> (end) ^ ^ ^ ^ ^ ^ | | | | | | | | +------- M1() ------+ | | | +----------- ... ----------+ | +--------------- Mn() --------------+
5.将函数封装并且用“laravel”式的语法来实现
文件 Middleware.php
namespace Middleware; use Generator; class Middleware { /** * 默认加载的中间件 * * @var array */ protected $handlers = []; /** * 执行时传递给每个中间件的参数 * * @var array|callable */ protected $arguments; /** * 设置在中间件中传输的参数 * * @param $arguments * @return self $this */ public function send(...$arguments) { $this->arguments = $arguments; return $this; } /** * 设置经过的中间件 * * @param $handle * @return $this */ public function through($handle) { $this->handlers = is_array($handle) ? $handle : func_get_args(); return $this; } /** * 运行中间件到达 * * @param \Closure $destination * @return null|mixed */ public function then(\Closure $destination) { $stack = []; $arguments = $this->arguments; foreach ($this->handlers as $handler) { $generator = call_user_func_array($handler, $arguments); if ($generator instanceof Generator) { $stack[] = $generator; $yieldValue = $generator->current(); if ($yieldValue === false) { break; }elseif($yieldValue instanceof Arguments){ //替换传递参数 $arguments = $yieldValue->toArray(); } } } $result = $destination(...$arguments); $isSend = ($result !== null); $getReturnValue = version_compare(PHP_VERSION, '7.0.0', '>='); //重入函数栈 while ($generator = array_pop($stack)) { /* @var $generator Generator */ if ($isSend) { $generator->send($result); }else{ $generator->next(); } if ($getReturnValue) { $result = $generator->getReturn(); $isSend = ($result !== null); }else{ $isSend = false; } } return $result; } }
文件 Arguments.php
namespace Middleware; /** * ArrayAccess 是PHP提供的一个预定义接口,用来提供数组式的访问 * 可以参考http://php.net/manual/zh/class.arrayaccess.php */ use ArrayAccess; /** * 这个类是用来提供中间件参数的 * 比如中间件B需要一个由中间件A专门提供的参数, * 那么中间件A可以通过 “yield new Arguments('foo','bar','baz')”将参数传给中间件B */ class Arguments implements ArrayAccess { private $arguments; /** * 注册传递的参数 * * Arguments constructor. * @param array $param */ public function __construct($param) { $this->arguments = is_array($param) ? $param : func_get_args(); } /** * 获取参数 * * @return array */ public function toArray() { return $this->arguments; } /** * @param mixed $offset * @return mixed */ public function offsetExists($offset) { return array_key_exists($offset,$this->arguments); } /** * @param mixed $offset * @return mixed */ public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->arguments[$offset] : null; } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value) { $this->arguments[$offset] = $value; } /** * @param mixed $offset */ public function offsetUnset($offset) { unset($this->arguments[$offset]); } }
使用 Middleware
$handle = [ function($object){ $object->hello = 'hello '; }, function($object){ $object->hello .= 'world'; }, ]; (new Middleware) ->send(new stdClass) ->through($handle) ->then(function($object){ echo $object->hello; }); /* * output * * hello world */
参考资料:
Aug 12 PHP5.5或将引入Generators
http://www.laruence.com/2012/08/30/2738.htmlPHP 5.5 新特性
http://www.cnblogs.com/yjf512/p/3164400.htmlPHP官方文档
http://php.net/manual/zh/class.generator.phpPHP协程初体验(利用协程完成socket异步,有一定socket编程基础可以看看)
http://blog.csdn.net/cszhouwei/article/details/41446687一个国人写的框架,让我学了蛮多
https://github.com/yeaha/owl
转自 http://blog.csdn.net/qq_20329253/article/details/52202811