文档章节

php利用yield 二三事(转)

碧海晴空
 碧海晴空
发布于 2017/09/11 16:08
字数 1728
阅读 2
收藏 0
点赞 0
评论 0

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
 */

本人曾参考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

本文转载自:

共有 人打赏支持
碧海晴空
粉丝 1
博文 3
码字总数 82
作品 0
福州
从外卖小哥到新媒体创业:这位高中毕业生的4条弯道超车秘诀

一个高中毕业生,位处三线城市,从外卖小哥做到中层,现在靠新媒体创业养活自己……虽然很多人都说新媒体门槛低,但在我因为工作原因采访一位新媒体创业者之前,我从来都不相信会有这么活生生...

半撇私塾 ⋅ 05/25 ⋅ 0

2)zookeeper集群搭建

1.安装zk 解压缩 配置环境变量 export ZOOKEEPER_HOME=/opt/zk export PATH=$ZOOKEEPER_HOME/bin 2.配置zoo.cfg 修改: dataDir=/opt/zk/data 新增: server.0=hadoop001:2888:3888 server.......

JPblog ⋅ 2016/07/25 ⋅ 0

PHP关键字之01 - yield

从PHP5.5开始,可以使用生成器来处理一个序列。生成器是一个函数,它不会调用return来返回一个值,而会调用yield(可能在一个循环中调用)。有了这样一个生成器,可以在原先使用数组的地方调...

Nosee123 ⋅ 05/29 ⋅ 0

学习yield《转》

预备知识 Generator 由于接触PHP时日尚浅,并未深入语言实现细节,所以只能根据现象进行猜测,以下是我的一些个人理解:包含yield关键字的函数比较特殊,返回值是一个Generator对象,此时函数...

o0无忧亦无怖 ⋅ 2016/07/04 ⋅ 1

QGIS SERVER QGIS 用于 发布环境WEB 地图

我们可以直接将QGIS编辑和配置的地图成果, 发布为标准的OGC地图服务。而不需要再转换为GEOSERVER工程发布。这样可以避免样式的重新配置和数据的重新组织。并且通过QGIS的扩展开发可以灵活的...

GIS开源 ⋅ 04/18 ⋅ 0

4)spark集群搭建

1.安装spark 解压缩(/opt) 设置环境变量 export SPARK_HOME=/opt/spark export PATH=$SPARK_HOME/bin export CLASSPATH=.:$CLASSPATH:$JAVAHOME/lib:$JAVAHOME/jre/lib 2.修改spark-env.sh ......

JPblog ⋅ 2016/07/26 ⋅ 0

马克思的一则轶事

某次,有个绅士问马克思,在将来的国家里谁来擦皮鞋。 马克思愤怒地回答他:"你来擦!" 那位冒失的绅士困惑地哑口无言。这大概是马克思失去耐性的唯一的一次。 那位绅士走后,我母亲直爽地说...

阮一峰 ⋅ 2006/05/22 ⋅ 0

5)kafka集群搭建

1.安装scala 1.1 解压缩(/opt) 1.2 配置环境变量 export SCALA_HOME=/usr/local/scala export PATH=$SCALA_HOME/bin 1.3 验证scala是否安装成功 1.4 二三节点安装scala 复制scala 复制环境变...

JPblog ⋅ 2016/07/26 ⋅ 0

CSDN日报180524——《一个合格的程序员,需要哪些必备技能?》

程序人生 | 一个合格的程序员,需要哪些必备技能? 作者:hollischuang 本文,主要来简单介绍一下,作为一个合格的 Java 开发,除了自身技术成长之外,还有哪些方面可以提升。 点击阅读全文 ...

blogdevteam ⋅ 05/24 ⋅ 0

自由职业者: 如何开始在国外接活如何竞标

接上篇《自由职业者: 如何开始在国外接活- 定价策略》,这篇文章讲述笔者对如何竞标的观点。 如果你接项目的做法是拍一个项目,留言后什么也不做,继续拍下一个项目,留言然后再拍下一个这样...

⋅ 2010/08/26 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vue使用mockjs

在使用vue开发的时候,一直疑惑与mockjs怎么用,开了mockjs的开发文档,还是一脸蒙蔽,无从下手!mockjs在前后端分离开发上进行模拟数据,是不可避掉的一环。在网上看了一些博文还有查阅了其...

JamesView ⋅ 23分钟前 ⋅ 0

解决问题的思路

1.相对来说,程序逻辑解决问题的思路应该更加趋向于通过逻辑结构来解决问题,而不是通过更小的类级别和方法级别的改进 2.类级别和方法级别的改进需要的技术能力更高一点

th778899 ⋅ 29分钟前 ⋅ 0

HTTP请求状态及jQuery AJAX请求异常处理

上一周调优一个项目的Js部分,其中一个严重的问题就是在页面初始化数据时,没有对异常进行处理, 导致Loading一直在等待中,无提示无处理。在用户体验上很不好,即使网络条件无法保证,在出错...

临江仙卜算子 ⋅ 29分钟前 ⋅ 0

error code 1874. innodb is in read only mode--报错解决

参考网页 https://zhidao.baidu.com/question/746894876932022292.html https://blog.csdn.net/shushugood/article/details/80226767 问题背景 创建了一个数据库然后想删除,因为自己本机性能......

karma123 ⋅ 30分钟前 ⋅ 0

JVM系列:jinfo命令详解

jinfo全称Java Configuration Info,主要作用是实时查看和调整JVM配置参数。 一.查看JVM参数 用法:jinfo -flag <name> PID 示例: # jinfo -flag MaxMetaspaceSize 11180 # -XX:MaxMetaspac......

Jacktanger ⋅ 35分钟前 ⋅ 0

exportfs命令、NFS客户端问题、FTP介绍、使用vsftpd搭建ftp

1. exportfs命令 一般情况下重启服务器上nfs服务时,需把客户端上的挂载先卸载掉,以免进程后面杀不掉。当客户端服务器很多时,操作起来就比较麻烦。此时可以使用exportfs命令重新加载下。 ...

laoba ⋅ 44分钟前 ⋅ 0

基于Python的信用评分卡模型分析

信用风险计量体系包括主体评级模型和债项评级两部分。主体评级和债项评级均有一系列评级模型组成,其中主体评级模型可用“四张卡”来表示,分别是A卡、B卡、C卡和F卡;债项评级模型通常按照主...

火力全開 ⋅ 45分钟前 ⋅ 0

执行make命令时报错g++: Command not found

执行make命令时报错g++: Command not found 2016年10月24日 12:31:29 阅读数:4366 朋友安装node时遇到的问题 报错截图: 其实很简单只需要安装一下 yum -y install gcc automake autoconf l...

rootliu ⋅ 45分钟前 ⋅ 0

Loongnix(龙芯)系统,优盘安装指南

U盘安装分为两种方式:"usb disk" 及 "usb cd"。其中usb cd是将usb disk做为usb光驱使用。 usb disk安装步骤: 需要准备一个大小超过3.5G的U盘,格式化ext3格式 下载安装光盘镜像xxx.iso,将...

gugudu ⋅ 49分钟前 ⋅ 0

HTML5中meta属性的使用详解

meta属性在HTML中占据了很重要的位置。如:针对搜索引擎的SEO,文档的字符编码,设置刷新缓存等。虽然一些网页可能没有使用meta,但是作为正规军,我们还是有必要了解一些meta的属性,并且能...

guorongjin ⋅ 52分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部