Laravel源码阅读之pipeline

在阅读laravel源码过程中,在Illuminate\Foudation\Http\Kenel.php中,开始处理requst请求中有这么一段代码

return (new Pipeline($this->app))
                  ->send($request)
                  ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                  ->then($this->dispatchToRouter());

透过字面理解知道,这段代码是实例化一个Pipeline(流水线),然后依次将本次请求的$requst对象,交给中间件middleware处理,之后再给路由器转发处理这个请求。

在laravel的文档中,我们也知道处理请求的这样一个过程,请求会先经过我们定义的中间件,然后再通过路由解析,交给具体的controller处理,这样其实就像是一个流水线的过程,$request对象依次的在各个工作台上被处理,每个工作台都把自己处理完的$request对象交给下一个工作台。

但是在阅读 Pipeline 类的时候,还是遇到的困难,经过近一小时的努力理解和手动尝试,最终彻底弄懂了,觉得这几行代码非常有趣,值得记录一下。

代码理解

  1. new Pipleline($this->app)
    将laravel容积对象传递给pipeline

  2. pipeline->send($request)

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

    设置pipeline中的 $passable为本次请求的request对象

  3. pipeline->through()

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

    将kenel.php中定义的的middleware数组传递给pipeline中的pipes

  4. pipeline->then()

    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );
    
        return $pipeline($this->passable);
    }
    

    这个方法中的array_reduce是最关键的操作,在此之前,我对这个数组的方法仅仅是讲过的层面,平时开发中从没用过。
    在这里,laravel用这个方法实现了一个流水线,具体的流程如下:

    1. 首先我们需要了解下 array_reduce 的作用和使用方法,
      • 方法定义array_reduce ( array $array , [callable] $callback , [mixed] $initial = null ) : [mixed]
      • 作用: 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值
        $arr = [2, 4, 6, 8];
        $result = array_reduce($arr, function($stack, $pipe) {
            // $stack就是上次遍历数组上一个元素时,对这个元素处理之后return的值,
            // 当遍历第一个数组中的第一个元素时,如果array_reduce第三个参数为null时,$stack 就是空,否则$stack最开始就是第三个参数的值
            return $stack + $pipe;
        }, 10);
        
        echo $result.PHP_EOL;    // echo 30
        
    2. 应用到代码中的then方法中,array_reduce有三个参数
      1. array_reverse($pipes)
        即kenel中定义的中间件的逆序。为什么要逆序呢,是因为第二个参数返回的是一个方法,该方法内部每次都会把前一次的结果压到栈中,导致最终执行的顺序是倒着的,所以先把$pipe倒序一下,就能保证后边顺序执行了
      2. $this->carry()
        protected function carry()
        {
            return function ($stack, $pipe) {
                return function ($passable) use ($stack, $pipe) {
                    if (is_callable($pipe)) {
                        return $pipe($passable, $stack);
                    } elseif (! is_object($pipe)) {
                        list($name, $parameters) = $this->parsePipeString($pipe);
                        $pipe = $this->getContainer()->make($name);
                        $parameters = array_merge([$passable, $stack], $parameters);
                    } else {
                        $parameters = [$passable, $stack];
                    }
                    return method_exists($pipe, $this->method)
                        ? $pipe->{$this->method}(...$parameters)
                        : $pipe(...$parameters);
                };
            };
         }
        
      在之前的例子中,第二个参数的返回值就是一个值,这里的返回值确实一个匿名函数,这个比之前更难以理解。

      但仔细阅读我们发现,在这个闭包中,每次都把$stack作为第二个参数,交给$pipe()或者$pipe->handle()方法,在middleware的handle方法中,我们知道他定义是handle($request, Closure $next)。

      遍历第一个middleware后,$stack就是carry最内层的方法,也就被当作了下一个中间件的$next参数,所以在每个中间件的最后,我们执行 $next($request),其实就是把这个中间件处理过的$request交给了之前的那个中间件。

      这一块比较绕,我按照这个思路写了一个小demo,利于理解
      class middleware1 {
          function handle($request, Closure $next) {
      
              echo "middleware 1, request=".$request.PHP_EOL;
              $request = $request."-1";
              return $next($request);
          }
      }
      
      class middleware2 {
          function handle($request, Closure $next) {
      
              echo "middleware 2, request=".$request.PHP_EOL;
              $request = $request."-2";
              return $next($request);
          }
      }
      
      class middleware3 {
          function handle($request, Closure $next) {
      
              echo "middleware 3, request=".$request.PHP_EOL;
              $request = $request."-3";
              return $next($request);
          }
      }
      
      const METHOD = "handle";
      $pipes = [
          new middleware1(),
          new middleware2(),
          new middleware3()
      ];
      
      function carry() {
          return function ($stack, $pipe) {
              return function ($passable) use ($stack, $pipe) {
                  $params = [$passable, $stack];
      
                  return $pipe->{METHOD}(...$params);
              };
          };
      }
      
      $pipeline = array_reduce(array_reverse($pipes), carry(), function($passable) {
          echo "final passable, final request=".$passable.PHP_EOL;
      });
      
      $pipeline("request");
      // middleware 1, request=request
      // middleware 2, request=request-1
      // middleware 3, request=request-1-2
      // final passable, final request=request-1-2-3
      
    3. this->prepareDestination($destination))
      第三个参数返回值也是一个匿名函数, 这样能保证最后一个middleware中执行$next(request)时,能继续接收request,把流水线继续下去,否则最后一个中间件中的$next就会失败了

总结

自己水平有限,写的有些乱,大家可以通过执行一下上边的那个小demo,参照laravel源码,应该能理解的更好

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容