Laravel中间件实现原理详解


Posted in PHP onOctober 09, 2016

本文实例讲述了Laravel的中间件实现原理。分享给大家供大家参考,具体如下:

#1 什么是中间件?

对于一个Web应用来说,在一个请求真正处理前,我们可能会对请求做各种各样的判断,然后才可以让它继续传递到更深层次中。而如果我们用if else这样子来,一旦需要判断的条件越来越来,会使得代码更加难以维护,系统间的耦合会增加,而中间件就可以解决这个问题。我们可以把这些判断独立出来做成中间件,可以很方便的过滤请求。

#2 Laravel中的中间件

在Laravel中,中间件的实现其实是依赖于Illuminate\Pipeline\Pipeline这个类实现的,我们先来看看触发中间件的代码。很简单,就是处理后把请求转交给一个闭包就可以继续传递了。

public function handle($request, Closure $next) {
  //do something for $request
  return $next($request);
}

#3 中间件内部实现

上面说道,中间件是靠Pipeline来实现的,它的调用在Illuminate\Routing\Router中

return (new Pipeline($this->container))
            ->send($request)
            ->through($middleware)
            ->then(function ($request) use ($route) {
              return $this->prepareResponse(
                $request,
                $route->run($request)
              );
            });

可以看到,中间件执行过程调用了三个方法。再来看看这三个方法的代码:

send方法

public function send($passable){
  $this->passable = $passable;
  return $this;
}

其实send方法没做什么事情,就是设置了需要在中间件中流水处理的对象,在这里就是HTTP请求实例。

through方法

public function through($pipes){
  $this->pipes = is_array($pipes) ? $pipes : func_get_args();
  return $this;
}

through方法也很简单,就是设置一下需要经过哪些中间件处理。

then方法

真正难懂的来了,then方法代码很简洁,但是要理解可不容易。

public function then(Closure $destination){
  //then方法接受一个闭包作为参数,然后经过getInitialSlice包装,而getInitialSlice返回的其实也是一个闭包,如果还不知道什么是闭包先去看PHP文档
  $firstSlice = $this->getInitialSlice($destination);
  //反转中间件数组,主要是利用了栈的特性,用处接下来再说
  $pipes = array_reverse($this->pipes);
  //这个call_user_func先不要看,它其实就是执行了一个array_reduce返回的闭包
  return call_user_func(  
    //接下来用array_reduce来用回调函数处理数组,建议先去PHP文档读懂array_reduce的执行原理。其实arrary_reduce什么事情都没干,就是包装闭包然后移交给call_user_func来执行
    array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
  );
}

然后就没有然后了,这样就过完了所有中间件,是不是很优雅?

由于aray_reduce的第二个参数需要一个函数,我们这里重点看看getSlice()方法的源码

protected function getSlice(){
    return function ($stack, $pipe) {  //这里$stack
      return function ($passable) use ($stack, $pipe) {
        if ($pipe instanceof Closure) {
          return call_user_func($pipe, $passable, $stack);
        } else {
          list($name, $parameters) = $this->parsePipeString($pipe);
          return call_user_func_array([$this->container->make($name), $this->method],
          array_merge([$passable, $stack], $parameters));
        }
      };
    };
}

看到可能会很头晕,闭包返回闭包的。简化一下就是getSlice()返回一个函数A,而函数A又返回了函数B。为什么要返回两个函数呢?因为我们中间在传递过程中是用$next($request)来传递对象的,而$next($request)这样的写法就表示是执行了这个闭包,这个闭包就是函数A,然后返回函数B,可以给下一个中间件继续传递。

再来简化一下代码就是:

//这里的$stack其实就是闭包,第一次遍历的时候会传入$firstSlice这个闭包,以后每次都会传入下面的那个function; 而$pipe就是每一个中间件
array_reduce($pipes, function ($stack, $pipe) {  
  return function ($passable) use ($stack, $pipe) {
  };
}, $firstSlice);

再来看这一段代码:

//判断是否为闭包,这里就是判断中间件形式是不是闭包,是的话直接执行并且传入$passable[请求实例]和$stack[传递给下一个中间件的闭包],并且返回
if ($pipe instanceof Closure) {  
  return call_user_func($pipe, $passable, $stack);
//不是闭包的时候就是形如这样Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode执行
} else {  
  //解析,把名称返回,这个$parameters看了许久源码还是看不懂,应该是和参数相关,不过不影响我们的分析
  list($name, $parameters) = $this->parsePipeString($pipe); 
  //从容器中解析出中间件实例并且执行handle方法
  return call_user_func_array([$this->container->make($name), $this->method], 
  //$passable就是请求实例,而$stack就是传递的闭包
  array_merge([$passable, $stack], $parameters));  
}

再看一张图片:

Laravel中间件实现原理详解

每一次迭代传入上一次的闭包和需要执行的中间件,由于反转了数组,基于栈先进后出的特性,所以中间件3第一个被包装,中间件1就在最外层了。要记得,arrary_reduce他不执行中间件代码,而是包装中间件。

看到这里应该明白了,array_reduce最后会返回func3,那么call_user_func(func3,$this->passable)实际就是

return call_user_func($middleware[0]->handle, $this->passable, func2);

而我们的中间件中的handle代码是:

public function handle($request, Closure $next) {
  return $next($request);
}

这里就相当于return func2($request),这里的$request就是经过上一个中间件处理过的。所以正果中间件的过程就完了,理解起来会有点绕,只要记得最后是由最外面的call_user_func来执行中间件代码的

希望本文所述对大家基于Laravel框架的PHP程序设计有所帮助。

PHP 相关文章推荐
简单示例AJAX结合PHP代码实现登录效果代码
Jul 25 PHP
用php将任何格式视频转为flv的代码
Sep 03 PHP
php 从数据库提取二进制图片的处理代码
Sep 09 PHP
php实现执行某一操作时弹出确认、取消对话框
Dec 30 PHP
php文件扩展名判断及获取文件扩展名的N种方法
Sep 12 PHP
yii2.0数据库迁移教程【多个数据库同时同步数据】
Oct 08 PHP
php使用FFmpeg接口获取视频的播放时长、码率、缩略图以及创建时间
Nov 07 PHP
php 反斜杠处理函数addslashes()和stripslashes()实例详解
Dec 25 PHP
基于win2003虚拟机中apache服务器的访问
Aug 01 PHP
PHP实现超简单的SSL加密解密、验证及签名的方法示例
Aug 28 PHP
Thinkphp 框架扩展之标签库驱动原理与用法分析
Apr 23 PHP
Swoole源码中如何查询Websocket的连接问题详解
Aug 30 PHP
Laravel 5.1 on SAE环境开发教程【附项目demo源码】
Oct 09 #PHP
ThinkPHP的SAE开发相关注意事项详解
Oct 09 #PHP
Laravel的throttle中间件失效问题解决方法
Oct 09 #PHP
Laravel日志用法详解
Oct 09 #PHP
Laravel手动分页实现方法详解
Oct 09 #PHP
Laravel5.1自定义500错误页面示例
Oct 09 #PHP
Laravel重写用户登录简单示例
Oct 08 #PHP
You might like
人族 TERRAN 概述
2020/03/14 星际争霸
PHP 5昨天隆重推出--PHP 5/Zend Engine 2.0新特性
2006/10/09 PHP
PHP不用第三变量交换2个变量的值的解决方法
2013/06/02 PHP
如何用php获取文件名后缀
2013/06/09 PHP
php mailer类调用远程SMTP服务器发送邮件实现方法
2016/03/04 PHP
PHP实现的注册,登录及查询用户资料功能API接口示例
2017/06/06 PHP
ThinkPHP 5.x远程命令执行漏洞复现
2019/09/23 PHP
javascript 类型判断代码分析
2010/03/28 Javascript
IE 下Enter提交表单存在重复提交问题的解决方法
2014/05/04 Javascript
基于JQuery实现仿网易邮箱全屏动感滚动插件fullPage
2015/09/20 Javascript
详解Bootstrap插件
2016/04/25 Javascript
jQuery判断checkbox选中状态
2016/05/12 Javascript
浅谈javascript中关于日期和时间的基础知识
2016/07/13 Javascript
ES6学习笔记之正则表达式和字符串正则方法分析
2017/04/25 Javascript
解决layer图标icon不加载的问题
2019/09/04 Javascript
[01:04:05]VG vs Newbee 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
python中实现将多个print输出合成一个数组
2018/04/19 Python
使用pandas实现csv/excel sheet互相转换的方法
2018/12/10 Python
强悍的Python读取大文件的解决方案
2019/02/16 Python
Python实现带下标索引的遍历操作示例
2019/05/30 Python
解决tensorflow读取本地MNITS_data失败的原因
2020/06/22 Python
Python 无限级分类树状结构生成算法的实现
2021/01/21 Python
小米官方旗舰店:Xiaomi
2020/08/07 全球购物
static函数与普通函数有什么区别
2015/12/25 面试题
工程造价与管理专业应届生求职信
2013/11/23 职场文书
竞争性谈判邀请书
2014/02/06 职场文书
党员一帮一活动总结
2014/07/08 职场文书
关于读书的演讲稿300字
2014/08/27 职场文书
买房协议书范本
2014/10/23 职场文书
2015年幼儿园元旦亲子活动方案
2014/12/09 职场文书
论文答谢词
2015/01/20 职场文书
高中化学教学反思
2016/02/22 职场文书
详解MySQL的半同步
2021/04/22 MySQL
MySQL 存储过程的优缺点分析
2021/05/20 MySQL
java设计模式--建造者模式详解
2021/07/21 Java/Android
NodeJs使用webpack打包项目的方法详解
2022/02/28 NodeJs