編程學習網 > PHP技術 > laravel > 一篇徹底了解laravel中間件實現原理
2021
08-07

一篇徹底了解laravel中間件實現原理

1. 什么是中間件?

對于一個Web應用來說,在一個請求真正處理前,我們可能會對請求做各種各樣的判斷,然后才可以讓它繼續傳遞到更深層次中。而如果我們用if else這樣子來,一旦需要判斷的條件越來越來,會使得代碼更加難以維護,系統間的耦合會增加,而中間件就可以解決這個問題。我們可以把這些判斷獨立出來做成中間件,可以很方便的過濾請求。

2. laravel中的中間件
在Laravel中,中間件的實現其實是依賴于Illuminate\Pipeline\Pipeline這個類實現的,我們先來看看觸發中間件的代碼。很簡單,就是處理后把請求轉交給一個閉包就可以繼續傳遞了。
 public function handle($request, Closure $next) {
        //do something for $request
        return $next($request);
    }


3. 中間件內部實現
上面說道,中間件是靠Pipeline來實現的,它的調用在Illuminate\Routing\Router中

    
 return (new Pipeline($this->container))
        ->send($request)
        ->through($middleware)
        ->then(function ($request) use ($route) {
            return $this->prepareResponse(
                $request,
                $route->run($request)
            );
    });


可以看到,中間件執行過程調用了三個方法。再來看看這三個方法的代碼:

send方法
public function send($passable){
    $this->passable = $passable;
    return $this;
}
其實send方法沒做什么事情,就是設置了需要在中間件中流水處理的對象,在這里就是HTTP請求實例。

through方法
public function through($pipes){
    $this->pipes = is_array($pipes) ? $pipes :func_get_args();
    return $this;
}
through方法也很簡單,就是設置一下需要經過哪些中間件處理。

then方法
真正難懂的來了,then方法代碼很簡潔,但是要理解可不容易。
public function then(Closure $destination){
   //then方法接受一個閉包作為參數,然后經過getInitialSlice包裝,而getInitialSlice返回的其實也是一個閉包,如果還不知道什么是閉包先去看PHP文檔
   $firstSlice = $this->getInitialSlice($destination);
   //反轉中間件數組,主要是利用了棧的特性,用處接下來再說
   $pipes = array_reverse($this->pipes);
   //這個call_user_func先不要看,它其實就是執行了一個array_reduce返回的閉包
   return call_user_func(
       //array_reduce來用回調函數處理數組。其實arrary_reduce什么事情都沒干,就是包裝閉包然后移交給call_user_func來執行
       array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
   );
}

然后就沒有然后了,這樣就過完了所有中間件,是不是很優雅?
由于aray_reduce的第二個參數需要一個函數,我們這里重點看看getSlice()方法的源碼
protected function getSlice(){
    return function ($stack, $pipe) {   //這里$stack
        return function ($passable) use ($stack, $pipe) {
            if ($pipe instanceof Closure) {
                return call_user_func($pipe, $passable, $stack);
            } else {
                list($name, $parameters) = $this->parsePipeString($pipe);
                    return call_user_func_array([$this->container->make($name),                             $this->method],
                        array_merge([$passable, $stack],
                        $parameters)
                    );
                }
            };
        };
    }
看到可能會很頭暈,閉包返回閉包的。簡化一下就是getSlice()返回一個函數A,而函數A又返回了函數B。為什么要返回兩個函數呢?因為我們中間在傳遞過程中是用n e x t ( next(next(request)來傳遞對象的,而n e x t ( next(next(request)這樣的寫法就表示是執行了這個閉包,這個閉包就是函數A,然后返回函數B,可以給下一個中間件繼續傳遞。

再來簡化一下代碼就是:
//這里的$stack其實就是閉包,第一次遍歷的時候會傳入$firstSlice這個閉包,以后每次都會傳入下面的那個function; 而$pipe就是每一個中間件
array_reduce($pipes, function ($stack, $pipe) {
    return function ($passable) use ($stack, $pipe) {
    };
}, $firstSlice);
再來看這一段代碼:
//判斷是否為閉包,這里就是判斷中間件形式是不是閉包,是的話直接執行并且傳入$passable[請求實例]和$stack[傳遞給下一個中間件的閉包],并且返回
if ($pipe instanceof Closure) {
   return call_user_func($pipe, $passable, $stack);
//不是閉包的時候就是形如這樣Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode執行
}  else {
   //解析,把名稱返回,這個$parameters看了許久源碼還是看不懂,應該是和參數相關,不過不影響我們的分析
   list($name, $parameters) = $this->parsePipeString($pipe);
   //從容器中解析出中間件實例并且執行handle方法
    return call_user_func_array([$this->container->make($name), $this->method],
        //$passable就是請求實例,而$stack就是傳遞的閉包
        array_merge([$passable, $stack], $parameters)
    );
}
再看一張圖片:


Laravel中間件原理
每一次迭代傳入上一次的閉包和需要執行的中間件,由于反轉了數組,基于棧先進后出的特性,所以中間件3第一個被包裝,中間件1就在最外層了。要記得,arrary_reduce他不執行中間件代碼,而是包裝中間件。
看到這里應該明白了,array_reduce最后會返回func3,那么call_user_func(func3,$this->passable)實際就是

return call_user_func($middleware[0]->handle, $this->passable, func2);
1
而我們的中間件中的handle代碼是:
public function handle($request, Closure $next) {
    return $next($request);
}

這里就相當于return func2(r e q u e s t ) , 這 里 的 request),這里的request),這里的request就是經過上一個中間件處理過的。所以正果中間件的過程就完了,理解起來會有點繞,只要記得最后是由最外面的call_user_func來執行中間件代碼的.

以上就是“一篇徹底了解laravel中間件實現原理”的詳細內容,想要了解更多laravel教程歡迎關注編程學習網

掃碼二維碼 獲取免費視頻學習資料

Python編程學習

查 看2022高級編程視頻教程免費獲取