理解Laravel中的pipeline

suoga 关注

 0.1 2015.09.08 00:00* 字数 1533 阅读 7151评论 8喜欢 24

pipeline在laravel的启动过程中出现次数很多,要了解laravel的启动过程和生命周期,理解pipeline就是其中的一个关键点。网上对pipeline的讲解很少,所以我自己写一写吧。
首先还是来看看调用栈,也就是从一个请求开始到返回响应,laravel都干了些什么。
在路由中配置如下

  1. Route::get('/', function() {
  2. return debug_backtrace();
  3. });

然后启动laravel自带的web server

  1. php artisan serve
 
启动laravel自带的serve

然后访问localhost:8000/
可以看到打印的调用栈,很长……

 
打印调用栈

左下角可以看出,从请求开始到结束,尽管什么都没干,但是依然加载了39个类……那些追求卓越性能的同学们需要好好考虑一下……
我们的重点是pipeline,如果你按照上面的步骤看了,会发现有一个pipeline调用的次数非常多。
那它到底是做什么用的呢?
简单一点讲,它是为了实现Laravel中的middleware。仔细想想middleware的代码

  1. public function handle($request, Closure $next) {
  2. //do something for $request
  3. return $next($request);
  4. }

一个请求经历层层的中间件的处理,才得到最终的请求,这到底是实现的呢?答案就是pipeline
首先来宏观感受下pipeline,到底怎么用的,再去细说它

 
pipeline在源码的使用

可以看到,主要有3个方法,也是pipeline暴露给使用者的所有方法

  • send
  • through
  • then

send方法

  1. /**
  2. * Set the object being sent through the pipeline.
  3. *
  4. * @param mixed $passable
  5. * @return $this
  6. */
  7. public function send($passable) {
  8. $this->passable = $passable;
  9. return $this;
  10. }

从说明看就是被在pipeline中传递的对象,可能不太好理解。那么我们就假定pipeline使用在middleware中,那么这个send的对象就是$request
想想看上面的中间件代码,每个handle方法接收的第一个参数是$request,其实就是这儿设置的讲被send的对象,它会在之后被到处传递,A处理完了送给B处理再送给C处理……pipeline,形象吧

through

 
屏幕快照 2015-09-07 下午9.05.36.png

顾名思义:通过、经由,就是上面send设置的对象要经由哪些中间件来处理
上面截图宏观说明pipeline的用法的时候可以看到,在调用through的时候

  1. (new Pipeline($this))
  2. ->send($request)
  3. ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
  4. ->then($this->dispatchToRouter());

through里传的参数,很好懂,如果当前请求我们设置了不需要中间件,那么就传一个空数组,不然就传递我们预先定义好的中间件数组
pipeline里的代码如下

  1. /**
  2. * Set the array of pipes.
  3. *
  4. * @param dynamic|array $pipes
  5. * @return $this
  6. */
  7. public function through($pipes){
  8. $this->pipes = is_array($pipes) ? $pipes : func_get_args();
  9. return $this;
  10. }

这个看起来也很简单,你要么传一个数组,要么传多个字符串,它会拼成一个数组,无所谓,反正指明中间件的名字就好

then

接下来就是核心,也是代码晦涩的地方了。
then方法就是当一切准备就绪后,下达开始处理的命令。
我们前面也能看到,sendthrough都是设置,并没有做其他什么,所以当 要传递的对象、需要经历的中间件都设置好之后,then?当然就是开始处理咯!
还是先看看then的代码

  1. /** * Run the pipeline with a final destination callback.
  2. *
  3. * @param \Closure $destination
  4. * @return mixed
  5. */
  6. public function then(Closure $destination){
  7. $firstSlice = $this->getInitialSlice($destination);
  8. $pipes = array_reverse($this->pipes);
  9. return call_user_func(
  10. array_reduce($pipes, $this->getSlice(), $firstSlice),
  11. $this->passable);
  12. );
  13. }

看起来短短几行,但是要理解起来可不太容易(这也是我比较喜欢laravel的原因,代码简单而优雅,一般人欣赏不来,哈哈)
我先大概讲一下
then方法需要接收一个匿名函数 $destination,至于什么样的匿名函数,你先可以不用管,往后看。我一句一句解释

  1. public function then(Closure $destination){
  2. //把传进来的匿名函数包装成一个闭包,不太理解没关系,反正知道$firstSlice是一个函数就好了
  3. $firstSlice = $this->getInitialSlice($destination);
  4. //把through方法传进来的参数,也就是中间件数组反转,为什么要这么做?
  5. //简单讲是利用栈的特性,继续往下看就知道了
  6. $pipes = array_reverse($this->pipes);
  7. //array_reduce(...)返回一个函数A,然后执行这个函数,把send进来的对象当参数传递函数A
  8. return call_user_func(
  9. array_reduce($pipes, $this->getSlice(), $firstSlice),
  10. $this->passable);
  11. );
  12. }

然后呢?
没有然后了,这就完成了整个工作,是不是很优雅(坑爹)的代码,哈哈
那我们的精力就放到这个array_reduce上面,它到底返回了一个什么神奇的函数,我只要把$request传递给它,就过完了所有中间件?
array_reduce($arr, 'funcA',5)大致等价于,第二个参数可以是函数名的字符串

  1. function fake_array_reduce($arr, $func, $initial=null)
  2. $temp = $initial;
  3. foreach($arr as $v) {
  4. $temp = $func($temp, $v);
  5. }
  6. return $temp;
  7. }

理解意思即可
回到then的源码,代码中传递的第二个参数一个是一个函数,而它是通过执行$this->getSlice()获得的一个函数,我们来看看这个getSlice方法

  1. /**
  2. * Get a Closure that represents a slice of the application onion.
  3. *
  4. * @return \Closure
  5. */
  6. protected function getSlice(){
  7. return function ($stack, $pipe) {
  8. return function ($passable) use ($stack, $pipe) {
  9. if ($pipe instanceof Closure) {
  10. return call_user_func($pipe, $passable, $stack);
  11. } else {
  12. list($name, $parameters) = $this->parsePipeString($pipe);
  13. return call_user_func_array(
  14. [$this->container->make($name), $this->method],
  15. array_merge([$passable, $stack], $parameters)
  16. );
  17. }
  18. };
  19. };
  20. }

刚看到这里我也有点头晕,这闭包套闭包的,什么鬼!
我们在简化一下,就是调用$this->getSlice()之后得到了一个函数,假设是函数B函数B接收两个参数,并返回一个函数C
我们来大致模拟一下这个过程
写一段假代码,仅仅是为了更好地说明这个过程:

  1. function haha($stack, $middleware) {
  2. return function($param) use ($stack, $middleware){
  3. //do something
  4. };
  5. }
  6. function fake_array_reduce($middlewares, $func, $inital=null) {
  7. $temp = $initial;
  8. //假设传递进来的函数$func = 'haha'
  9. foreach($middlewares as $middleware) {
  10. $temp = $func($temp, $middleware);
  11. }
  12. return $temp;
  13. }

这个过程的基本流程如上,haha返回了一个空函数(实际上肯定不是),我只是为了说明每次都返回一个函数作为下一次迭代的参数,而并没有执行任何其它功能,按照上面的代码,最后执行完fake_array_reduce会得到一个函数,这个函数接收一个参数。
所以再回过头来看
call_user_func(array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
实际上就是相当于调用了array_reduce最后一次迭代返回的函数,然后给这个函数传递了一个参数$this->passable
所以,这时候我们需要关注最后一次返回的函数是什么,看看源码关键是这段

  1. return function($stack, $pipe) {
  2. return function($passable) use ($stack, $pipe) {
  3. if($pipe instanceof Closure) {
  4. return call_user_func($pipe, $passable, $stack);
  5. } else {
  6. //..
  7. }
  8. }
  9. }

每次迭代传入了当前的$pipe和上一次迭代返回的函数

 
IMG_0687.JPG

由上图所示
由于把pipes先反转了一下,所以最后一次迭代array_reduce得到的函数f3所use的是($stack=f2, $pipe=$middleware[0])
那么call_user_func(f3,$this->passable)相当于是

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

仔细想想,middleware里面的handle方法

  1. public function handle($request, Closure $next) {
  2. //...
  3. return $next($request);
  4. }

在这里就相当于就是return f2($request)
也就相当于return call_user_func($middleware[1]->handle, $request, f1)
而这个$request是经过middleware[0]处理过后传递过来的。
……
这个过程比较绕,需要大家自己再画图理解一下,比如源代码里用到的onion(洋葱),$stack(栈),这些单词可以帮助大家更好地理解这个过程。
所以return call_user_func($pipes, $this->getSlice(), $this->passable)就会把$request请求按middleware定义的先后顺序,流水线处理,中间件处理完了之后再传递给$this->dispatchToRouter()这个方法处理(这又是一个抽象),从而得到最后的响应结果。

理解pipeline需要理解 闭包 栈等概念,熟悉了pipeline会对理解php的新特性有比较大的帮助。

理解Laravel中的pipeline的更多相关文章

  1. 深入理解 Laravel 中 config 配置加载原理

    Laravel的配置加载其实就是加载config目录下所有文件配置.如何过使用php artisan config:cache则会把加载的配置合并到一个配置文件中,下次请求就不会再去加载config目 ...

  2. laravel中redis pipeline用法说明

    $res = Redis::pipeline(function($pipe) use($params) { for ($i = 0; $i < 1000; $i++) { $pipe->g ...

  3. laravel中的构造函数依赖注入理解

    laravel中的自动依赖注入是非常强大的,刚开始会疑惑为什么只要在构造函数中传入一个强制类型的变量(就是参数有类型限制)过去就行了? 通过查看源码即查阅资料发现其实这其中有一个php技术,就是反射技 ...

  4. Laravel中的日志与上传

    PHP中的框架众多,我自己就接触了好几个.大学那会啥也不懂啥也不会,拿了一个ThinkPHP学了.也许有好多人吐槽TP,但是个人感觉不能说哪个框架好,哪个框架不好,再不好的框架你能把源码读上一遍,框架 ...

  5. 深入理解 Laravel Eloquent(三)——模型间关系(关联)

    Eloquent是什么 Eloquent 是一个 ORM,全称为 Object Relational Mapping,翻译为 "对象关系映射"(如果只把它当成 Database A ...

  6. Laravel中的队列处理

    Laravel中的队列处理 队列介绍 为什么要有消息队?这里先对其进行一个简单的介绍,方便还不了解的同学理解.在面向对象里,有一个很简单的概念--消息传递,而消息队列就可以在它上面扩展一下,把它说的更 ...

  7. 在Laravel中一步一步创建Packages

    首先要看一下Laravel官方文档,这是最新4.2的文档,假设想看中文的话点击此处,基本一样.这个github上的库setup-laravel4-package,也是一步一步介绍怎样创建一个包.并关联 ...

  8. Laravel 中使用 Redis 数据库

    一.前言 Redis 是一个开源高效的键值对存储系统,它通常用作为一个数据结构服务器来存储键值对,它可以支持字符串.散列.列表.集合.有序集合. 1. 安装 predis/predis 在 Larav ...

  9. Laravel 中使用 JWT 认证的 Restful API

    Laravel 中使用 JWT 认证的 Restful API 5天前/  678 /  3 / 更新于 3天前     在此文章中,我们将学习如何使用 JWT 身份验证在 Laravel 中构建 r ...

随机推荐

  1. 使用 DML语句,对 “锦图网” 数据进行操作,聚合函数练习

    查看本章节 查看作业目录 需求说明: 根据客户 ID 统计订单数.订单总金额.最高订单金额.最低订单金额和每份订单平均金额,并按订单总金额升序显示 根据客户统计订单总订购人次数> 5 的统计信息 ...

  2. 美和易思 MOOT去鼠标检测,快进,倍速,自动下一章

    F12 放到 console 直接运行即可 或者油猴添加新脚本 核心去除网页绑定焦点事件代码: if (!-[1,] && !window.XMLHttpRequest || navi ...

  3. Shell调试Debug的三种方式

    Shell脚本进行Debug调试的三种方法如下: 1.在调用脚本的时候开启deubg sh -x shell.sh 2.在脚本文件首行开启deubg #!/bin/bash -x 3. 使用set开启 ...

  4. python 中的省略号

    在查看django源码时遇到下列内容:sweat: 这个省略号是什么意思? 来自为知笔记(Wiz)

  5. win10快捷方式小箭头怎么去掉

    为了演示,先来看看桌面图标是有小箭头的. 1.打开注册表 按下快捷键"win+R",然后输入"regedit",并点击确认按钮. 2.搜索HKEY_CLASSE ...

  6. Centos7上传文件和下载文件命令

    https://www.cnblogs.com/patrick-yeh/p/12922829.html 使用工具:SecureCRT 或 Xshell 步骤一:安装lrzsz软件,root权限下.安装 ...

  7. Autofac实现有条件的DI

    Autofac.Annotation框架是我用.netcore写的一个DI框架,基于Autofac参考 Spring注解方式所有容器的注册和装配,切面,拦截器等都是依赖标签来完成. 开源地址:http ...

  8. stm32单片机利用ntc热敏电阻温度转换公式C语言版

    首先 我们需要明确电路结构 热敏电阻的原理就不再赘述,本文不凑字数,只讲干货 必须要知道的就是串联电阻R9程序中定义为resistanceInSeries ,精度越高越好 为了方便,先在程序中定义好你 ...

  9. 友善之臂mini2440光盘资料下载FriendlyARM-2440-DVD.7z

    这是我目前找到最全面的友善之臂mini2440的光盘资料了,几乎支持mini2440的各种lcd屏幕.另外,Windows平台工具文件夹下还有版本比较新的MiniTools,这是一个非常好用的软件! ...

  10. 《剑指offer》面试题21. 调整数组顺序使奇数位于偶数前面

    问题描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分. 示例: 输入:nums = [1,2,3,4] 输出:[1,3,2,4] ...