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。為什麼要返回兩個函式呢?因為我們中間在傳遞過程中是用$next($request)
來傳遞物件的,而$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)
);
}
再看一張圖片:
每一次迭代傳入上一次的閉包和需要執行的中介軟體,由於反轉了陣列,基於棧先進後出的特性,所以中介軟體3第一個被包裝,中介軟體1就在最外層了。要記得,arrary_reduce
他不執行中介軟體程式碼,而是包裝中介軟體。
看到這裡應該明白了,array_reduce
最後會返回func3
,那麼call_user_func(func3,$this->passable)
實際就是
return call_user_func($middleware[0]->handle, $this->passable, func2);
而我們的中介軟體中的handle
程式碼是:
public function handle($request, Closure $next) {
return $next($request);
}
這裡就相當於return func2($request)
,這裡的$request
就是經過上一個中介軟體處理過的。所以正果中介軟體的過程就完了,理解起來會有點繞,只要記得最後是由最外面的call_user_func
來執行中介軟體程式碼的.