什么是中間件?
對(duì)于一個(gè)Web應(yīng)用來(lái)說(shuō),在一個(gè)請(qǐng)求真正處理前,我們可能會(huì)對(duì)請(qǐng)求做各種各樣的判斷,然后才可以讓它繼續(xù)傳遞到更深層次中。而如果我們用if else這樣子來(lái),一旦需要判斷的條件越來(lái)越來(lái),會(huì)使得代碼更加難以維護(hù),系統(tǒng)間的耦合會(huì)增加,而中間件就可以解決這個(gè)問(wèn)題。我們可以把這些判斷獨(dú)立出來(lái)做成中間件,可以很方便的過(guò)濾請(qǐng)求。
我們都知道,使用Laravel中間件有三個(gè)步驟:
使用php artisan生成一個(gè)中間件,這里假設(shè)生成一個(gè)TestMiddleware的中間件
重寫(xiě)TestMiddleware中的handle函數(shù),其中代碼邏輯寫(xiě)在return $next($request);之前或者之后表示在執(zhí)行請(qǐng)求之前或者之后運(yùn)行這段代碼。
在app/Http/Kernel.php的routeMiddleware注冊(cè)一個(gè)中間件
然而,這上面的幾點(diǎn)下來(lái),你會(huì)不會(huì)一頭霧水,為什么要執(zhí)行這么些操作之后才能使用一個(gè)中間件。尤其是第二點(diǎn)?
Laravel中的中間件
在Laravel中,中間件的實(shí)現(xiàn)其實(shí)是依賴(lài)于Illuminate\Pipeline\Pipeline這個(gè)類(lèi)實(shí)現(xiàn)的,我們先來(lái)看看觸發(fā)中間件的代碼。很簡(jiǎn)單,就是處理后把請(qǐng)求轉(zhuǎn)交給一個(gè)閉包就可以繼續(xù)傳遞了。
public function handle($request, Closure $next) {
//do something for $request
return $next($request);
}
中間件內(nèi)部實(shí)現(xiàn)
上面說(shuō)道,中間件是靠Pipeline來(lái)實(shí)現(xiàn)的,它的調(diào)用在Illuminate\Routing\Router中
return (new Pipeline($this-》container))
-》send($request)
-》through($middleware)
-》then(function ($request) use ($route) {
return $this-》prepareResponse(
$request,
$route-》run($request)
?。?
});
可以看到,中間件執(zhí)行過(guò)程調(diào)用了三個(gè)方法。再來(lái)看看這三個(gè)方法的代碼:
send方法
public function send($passable){
$this-》passable = $passable;
return $this;
}
其實(shí)send方法沒(méi)做什么事情,就是設(shè)置了需要在中間件中流水處理的對(duì)象,在這里就是HTTP請(qǐng)求實(shí)例。
through方法
public function through($pipes){
$this-》pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
through方法也很簡(jiǎn)單,就是設(shè)置一下需要經(jīng)過(guò)哪些中間件處理。
then方法
真正難懂的來(lái)了,then方法代碼很簡(jiǎn)潔,但是要理解可不容易。
public function then(Closure $destination){
//then方法接受一個(gè)閉包作為參數(shù),然后經(jīng)過(guò)getInitialSlice包裝,而getInitialSlice返回的其實(shí)也是一個(gè)閉包,如果還不知道什么是閉包先去看PHP文檔
$firstSlice = $this-》getInitialSlice($destination);
//反轉(zhuǎn)中間件數(shù)組,主要是利用了棧的特性,用處接下來(lái)再說(shuō)
$pipes = array_reverse($this-》pipes);
//這個(gè)call_user_func先不要看,它其實(shí)就是執(zhí)行了一個(gè)array_reduce返回的閉包
return call_user_func(
//接下來(lái)用array_reduce來(lái)用回調(diào)函數(shù)處理數(shù)組,建議先去PHP文檔讀懂a(chǎn)rray_reduce的執(zhí)行原理。其實(shí)arrary_reduce什么事情都沒(méi)干,就是包裝閉包然后移交給call_user_func來(lái)執(zhí)行
array_reduce($pipes, $this-》getSlice(), $firstSlice), $this-》passable
);
}
然后就沒(méi)有然后了,這樣就過(guò)完了所有中間件,是不是很優(yōu)雅?
由于aray_reduce的第二個(gè)參數(shù)需要一個(gè)函數(shù),我們這里重點(diǎn)看看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));
}
};
};
}
看到可能會(huì)很頭暈,閉包返回閉包的。簡(jiǎn)化一下就是getSlice()返回一個(gè)函數(shù)A,而函數(shù)A又返回了函數(shù)B。為什么要返回兩個(gè)函數(shù)呢?因?yàn)槲覀冎虚g在傳遞過(guò)程中是用$next($request)來(lái)傳遞對(duì)象的,而$next($request)這樣的寫(xiě)法就表示是執(zhí)行了這個(gè)閉包,這個(gè)閉包就是函數(shù)A,然后返回函數(shù)B,可以給下一個(gè)中間件繼續(xù)傳遞。
?
再來(lái)簡(jiǎn)化一下代碼就是:
//這里的$stack其實(shí)就是閉包,第一次遍歷的時(shí)候會(huì)傳入$firstSlice這個(gè)閉包,以后每次都會(huì)傳入下面的那個(gè)function; 而$pipe就是每一個(gè)中間件
array_reduce($pipes, function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
};
}, $firstSlice);
再來(lái)看這一段代碼:
//判斷是否為閉包,這里就是判斷中間件形式是不是閉包,是的話直接執(zhí)行并且傳入$passable[請(qǐng)求實(shí)例]和$stack[傳遞給下一個(gè)中間件的閉包],并且返回
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
//不是閉包的時(shí)候就是形如這樣Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode執(zhí)行
} else {
//解析,把名稱(chēng)返回,這個(gè)$parameters看了許久源碼還是看不懂,應(yīng)該是和參數(shù)相關(guān),不過(guò)不影響我們的分析
list($name, $parameters) = $this-》parsePipeString($pipe);
//從容器中解析出中間件實(shí)例并且執(zhí)行handle方法
return call_user_func_array([$this-》container-》make($name), $this-》method],
//$passable就是請(qǐng)求實(shí)例,而$stack就是傳遞的閉包
array_merge([$passable, $stack], $parameters));
}
再看一張圖片:
每一次迭代傳入上一次的閉包和需要執(zhí)行的中間件,由于反轉(zhuǎn)了數(shù)組,基于棧先進(jìn)后出的特性,所以中間件3第一個(gè)被包裝,中間件1就在最外層了。要記得,arrary_reduce他不執(zhí)行中間件代碼,而是包裝中間件。
看到這里應(yīng)該明白了,array_reduce最后會(huì)返回func3,那么call_user_func(func3,$this-》passable)實(shí)際就是
代碼如下:
return call_user_func($middleware[0]-》handle, $this-》passable, func2);
而我們的中間件中的handle代碼是:
public function handle($request, Closure $next) {
return $next($request);
}
這里就相當(dāng)于return func2($request),這里的$request就是經(jīng)過(guò)上一個(gè)中間件處理過(guò)的。所以正果中間件的過(guò)程就完了,理解起來(lái)會(huì)有點(diǎn)繞,只要記得最后是由最外面的call_user_func來(lái)執(zhí)行中間件代碼的
中間件代碼分析
中間件可以實(shí)現(xiàn)啊很多功能,例如權(quán)限驗(yàn)證,訪問(wèn)記錄,重定向等等。
具體干什么看自己想法。
中間件在請(qǐng)求階段會(huì)調(diào)用自己的handle()方法
同時(shí)中間件也可以在響應(yīng)階段使用,這時(shí),會(huì)掉用它的terminate()方法。
所以,當(dāng)需要在響應(yīng)發(fā)出后使用中間件只需要重寫(xiě)terminate()方法即可。
《?php
namespace App\Http\Middleware;
use Closure;
class TestMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
//這里是響應(yīng)后調(diào)用的方法
}
}
handle()方法
handle()方法有兩個(gè)參數(shù)
$request ---》請(qǐng)求信息,里面包含了輸入,URL,上傳文件等等信息。
$next ---》閉包函數(shù)。我的理解是將接下來(lái)需要執(zhí)行的邏輯裝載到了其中。
返回值:
通過(guò)上文對(duì)參數(shù)的描述可以了解到:
當(dāng)我們?cè)谥虚g件中return $next($request);時(shí),相當(dāng)與把請(qǐng)求傳入接下來(lái)的邏輯中。
同時(shí),中間件也可以返回重定向,不運(yùn)行之前的邏輯。
例如,希望將頁(yè)面重定向到‘/welcome’的頁(yè)面return redirect(‘welcome’)。
注意,這里是重定向到“/welcome”這個(gè)地址的route而不是“welcome”這個(gè)頁(yè)面(view)。
terminate()方法
參數(shù)
$request ---》請(qǐng)求信息,里面包含了輸入,URL,上傳文件等等信息。
$response --》響應(yīng)消息,包含了邏輯處理完成后傳出到的響應(yīng)消息。
因?yàn)閠erminate()方法只是在響應(yīng)后進(jìn)行一些處理所以沒(méi)有返回值。
評(píng)論
查看更多