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,把流水线继续下去,否则最后一个中间件中的$next就会失败了

总结

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

你可能感兴趣的:(Laravel源码阅读之pipeline)