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源碼,應該能理解的更好

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容