您好,欢迎访问本站博客!登录后台查看权限
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧
  • 网站所有资源均来自网络,如有侵权请联系站长删除!

php利用yield写一个简单中间件

php admin 2018-12-10 183 次浏览 0个评论
网站分享代码

转自 :

原文:https://blog.csdn.net/qq_20329253/article/details/52202811 

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

 */



已有 183 位网友参与,快来吐槽:

发表评论

站点统计