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";    
}    

如果了解过迭代器的朋友,就可以通过上面这一段代码看出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');

这样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'));

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]);

通过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;  
    });


本人曾参考laravel的管道类实现方式,所以使用语法极其相似,不过实现过程不一致,等到有空的时候专门写一篇详解管道模式的博客

参考资料:
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

猜你喜欢

转载自my.oschina.net/u/3683692/blog/2253001