yield 协程
1.初识Generator
Generator , 一种可以返回迭代器的生成器,当程序运行到yield的时候,当前程序就唤起协程记录上下文,然后主函数继续操作,当需要操作的时候,在通过迭代器的next重新调起
function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
foreach (xrange(1, 1000) as $num) {
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
*/
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() 直到返回 false 迭代结束
Generators::rewind() 重置迭代器
Generators::valid() 检查迭代器是否被关闭
Generators::current() 返回当前产生的值
Generators::next() 生成器继续执行
Generators::valid()
Generators::current()
Generators::next()
...
Generators::valid() 直到返回 false 迭代结束
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遍历巨大的查询结果导致的内存溢出
数据库连接.....
$sql = "select * from `user` limit 0,500000000";
$stat = $pdo->query($sql);
$data = $stat->fetchAll(); //mysql buffered query遍历巨大的查询结果导致的内存溢出
var_dump($data);
数据库连接.....
$sql = "select * from `user` limit 0,500000000";
$stat = $pdo->query($sql);
$data = $stat->fetchAll(); //mysql buffered query遍历巨大的查询结果导致的内存溢出
var_dump($data);
yield获取方式:
$sql = "select * from `user` limit 0,500000000";
$stat = $pdo->query($sql);
while ($row = $stat->fetch()) {
foreach (get() as $row) {
数据库连接.....
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);
}
数据库连接.....
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);
$sayHello = $ben->current();
$ben->send('hi ben ,my name is alex');
* my name is ben ,what's your name
* hi ben ,my name is alex
$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 = 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);
$jump = (yield '[yield] I jump,you jump'.PHP_EOL);
echo '[Exception]'.$e->getMessage().PHP_EOL;
$hello = $Generatorg->current();
$jump = $Generatorg->send('[main] say hello');
$Generatorg->throw(new Exception('[main] No,I can\'t jump'));
* [yield] I jump,you jump
* [Exception][main] No,I can't jump
$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
*/
$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 = []){
foreach ($handlers as $handler) {
// 每次循环之前重置,只能保存最后一个处理程序的返回值
$generator = call_user_func_array($handler, $arguments);
if ($generator instanceof \Generator) {
$yieldValue = $generator->current();
if ($yieldValue === false) {
} elseif ($generator !== null) {
$return = ($result !== null);
while ($generator = array_pop($stack)) {
$generator->send($result);
echo "this is abc start \n";
echo "this is abc end \n";
echo "this is qwe start \n";
echo "this is qwe end \n";
middleware([$abc,$qwe,$one]);
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
*/
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() --------------+
(begin) ----------------> function() -----------------> (end)
^ ^ ^ ^ ^ ^
| | | | | |
| | +------- M1() ------+ | |
| +----------- ... ----------+ |
+--------------- Mn() --------------+
(begin) ----------------> function() -----------------> (end)
^ ^ ^ ^ ^ ^
| | | | | |
| | +------- M1() ------+ | |
| +----------- ... ----------+ |
+--------------- Mn() --------------+
5.将函数封装并且用“laravel”式的语法来实现
文件 Middleware.php
protected $handlers = [];
public function send(...$arguments)
$this->arguments = $arguments;
public function through($handle)
$this->handlers = is_array($handle) ? $handle : func_get_args();
* @param \Closure $destination
public function then(\Closure $destination)
$arguments = $this->arguments;
foreach ($this->handlers as $handler) {
$generator = call_user_func_array($handler, $arguments);
if ($generator instanceof Generator) {
$yieldValue = $generator->current();
if ($yieldValue === false) {
}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 */
$generator->send($result);
$result = $generator->getReturn();
$isSend = ($result !== null);
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;
}
}
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
* ArrayAccess 是PHP提供的一个预定义接口,用来提供数组式的访问
* 可以参考http://php.net/manual/zh/class.arrayaccess.php
* 比如中间件B需要一个由中间件A专门提供的参数,
* 那么中间件A可以通过 “yield new Arguments('foo','bar','baz')”将参数传给中间件B
class Arguments implements ArrayAccess
public function __construct($param)
$this->arguments = is_array($param) ? $param : func_get_args();
public function toArray()
public function offsetExists($offset)
return array_key_exists($offset,$this->arguments);
public function offsetGet($offset)
return $this->offsetExists($offset) ? $this->arguments[$offset] : null;
public function offsetSet($offset, $value)
$this->arguments[$offset] = $value;
public function offsetUnset($offset)
unset($this->arguments[$offset]);
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]);
}
}
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
$object->hello = 'hello ';
$object->hello .= 'world';
->then(function($object){
$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
*/
$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.html
PHP 5.5 新特性
http://www.cnblogs.com/yjf512/p/3164400.html
PHP官方文档
http://php.net/manual/zh/class.generator.php
PHP协程初体验(利用协程完成socket异步,有一定socket编程基础可以看看)
http://blog.csdn.net/cszhouwei/article/details/41446687
一个国人写的框架,让我学了蛮多
https://github.com/yeaha/owl
转自 http://blog.csdn.net/qq_20329253/article/details/52202811