美文网首页
Laravel源码阅读之pipeline

Laravel源码阅读之pipeline

作者: Captain_tu | 来源:发表于2021-04-19 16:12 被阅读0次

    在阅读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源码,应该能理解的更好

    相关文章

      网友评论

          本文标题:Laravel源码阅读之pipeline

          本文链接:https://www.haomeiwen.com/subject/tlbilltx.html