laravel中間件源碼分析
在laravel5.2中,HTTP 中間件為過濾訪問你的應用的 HTTP 請求提供了一個方便的機制。在處理邏輯之前,會通過中間件,且只有通過了中間件才會繼續執行邏輯代碼。它的主要作用就是過濾Http請求(php aritsan
是沒有中間件機制的),同時也讓系統的層次(Http過濾層)更明確,使用起來也很優雅。但實現中間件的代碼卻很復雜,接下來就分析下有關中間件的源碼(討論是在laravel5.2上展開的)。
中間件源碼
中間件本身分為兩種,一種是所有http的,另一種則是針對route的。一個有中間件的請求周期是:Request得先經過Http中間件,才能進行Router,再經過Requset所對應Route的Route中間件, 最后才會進入相應的Controller代碼。laravel把請求分為了兩種:http和console。不同的請求方式用它自己的Kernel
來驅動Application
。Http請求則是通過
\Illuminate\Foundation\Http\Kernel
類來驅動,它定義了所有的中間件,其父類\Illuminate\Foundation\Http\Kernel::handle
就是對請求進行處理的入口了
Http中間件
跟蹤入口handle()
方法,很容易發現該函數(\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter
):
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
該函數會把Requset分發到Router(通過方法名就知道了), 主要的邏輯則是通過\Illuminate\Routing\Pipeline
完成的, 作用就是讓Requset通過Http中間件的檢測,然后再到達Router。這里的代碼看起來很優雅,但不是很好理解。所以,了解Pipeline
的運行機制就會明白中間件的使用。
Pipeline的運行實現
Pipleline
基類是\Illuminate\Pipeline\Pipeline
,它的執行在then
方法:
public function then(Closure $destination)
{
$firstSlice = $this->getInitialSlice($destination);
$pipes = array_reverse($this->pipes);
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
}
了解這段代碼執行的意圖,必須要知道array_reduce()做了什么。 為了清楚array_reduce
怎么運行的,先把array_reduce
重寫一次:
//將數組中的元素,依次執行$func函數,且上一次的$func的返回值作為下一次調用$func的第一個參數輸入
function array_reduce_back($arr, callable $func, $firstResult = null)
{
$result = $firstResult;
foreach ($arr as $v) {
$result = $func($result, $v);
}
return $result;
}
所以,源代碼中的$func
是getSlice()
,它返回的是一個回調函數:function($passable) use ($stack, $pipe){...}
($stack
和$pipe
被輸入的具體值代替),也就是說作為上一次返回結果輸入到下一次$func
的第一個參數是上述的回調函數,如此循環,當數組遍歷完成,array_reduce
就返回的是一個回調函數,現在關鍵就是了解這個回調函數是什么樣子,又如何執行?為方便討論,可分析下面的代碼:
call_user_func(
array_reduce([1, 2, 3], $this->getSlice(), $firstSlice), $this->passable
);
執行說明:
1.$result_0
是初始化的值 ,為$firstSlice
,即是\Illuminate\Pipeline\Pipeline::getInitialSlice
的返回回調
2.每遍歷一個元素,都會執行\Illuminate\Pipeline\Pipeline::getSlice
的回調,同時也會返回一個回調
3.$result
中的具體執行代碼都在getSlice()
中
4.最后的array_reduce
返回結果是$result_3
,是一個有多層閉包的回調函數
5.執行的是call_user_func($result_3, $this->passable)
,即執行function($this->passable) use ($result_2, 3){...}
至此已經清楚了then()
是如何運行的了,要繼續下去,則需再搞定回調函數到底怎么執行的.現在再跟着sendRequestThroughRouter
中的Pipeline
走,看它是如何執行的。
// 把具體的參數帶進來
return (new Pipeline($this->app))
->send($request)
->through(['\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode'])
->then($this->dispatchToRouter());
用上面的所分析的Pipeline
執行過程,很快就會分析出最后執行的是
function($requset) use (\Illuminate\Foundation\Http\Kernel::dispatchToRouter(), '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode') {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
}
// $name和$parameters很容易得到
// $name = '\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode';
// $parameters = [];
list($name, $parameters) = $this->parsePipeString($pipe);
// 執行的就是\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle($request, \Illuminate\Foundation\Http\Kernel::dispatchToRouter())
return call_user_func_array([$this->container->make($name), $this->method],
array_merge([$passable, $stack], $parameters));
}
邏輯處理已經到了\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::handle
,其代碼是:
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
throw new HttpException(503);
}
return $next($request);
}
這里,它處理了這個中間件所需要過濾的條件,同時執行了$next($request)
,即\Illuminate\Foundation\Http\Kernel::dispatchToRouter()
, 這樣,就把Request轉到了Router中,也就完成了Http中間件的所有處理工作,而$next($request)
是每個中間件都不可少的操作,因為在回調中嵌套了回調,就是靠中間件把Request
傳遞到下一個回調中,也就會解析到下一個中間件,直到最后一個。緊跟上面的已分析的Pipeline
執行過程,講其補充完整:
6.執行$result_3中的回調,getSlice
實例化中間件,執行其handle
,在中間件處理中執行回調
7.回調中還嵌套回調的,每個中間件中都需有執行回調的代碼$next($request)
,才能保證回調中的回調會執行,執行的順序就是3::handel,2::handel,1::handel,$first
8.最里面一層,一定是傳遞給then()的參數,then執行的就是最后一步
9.執行的順序是由數組中的最后一個,向前,到then()的參數,為了使其執行順序是數組中的第一個到最后一個,再到then()中的參數,then()方法中就做了一個反轉array_reverse
Pipeline小結
現在,Pipeline的所有執行流程就都分析完了。實現代碼真的很繞,但理解之后編寫自定義的中間件應該就很容易了。現在再把Pipeline
的使用翻譯成漢語,應該是這樣的
// 使用管道,發送$request,使之通過middleware ,再到$func
(new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
這樣的代碼不管是從語義上,還是使用上都很優雅,高!確實是高!再回到源碼,Requset的流程就通過dispatchToRouter
進入到了Router
Route中間件
在Router中,\Illuminate\Routing\Router::dispatch
就承接了來自Http中間件的Requset, Router把Request分發到了具體的Route,再進行處理,主要代碼如下:
public function dispatchToRoute(Request $request)
{
// 找到具體的路由對象,過程略
$route = $this->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
// 執行Request匹配到Route的事件,具體的代碼在這里:\Illuminate\Foundation\Providers\FoundationServiceProvider::configureFormRequests
$this->events->fire(new Events\RouteMatched($route, $request));
// 這里就運行路由中間件了
$response = $this->runRouteWithinStack($route, $request);
return $this->prepareResponse($request, $response);
}
protected function runRouteWithinStack(Route $route, Request $request)
{
// 獲取該路由上的中間件
// 簡單就點可這樣寫:
// $middleware = App::shouldSkipMiddleware() ? [] : $this->gatherRouteMiddlewares($route);
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddlewares($route);
// 了解Pipeline后,這里就好理解了,應該是通過管道,發送$request,經過$middleware,再到then中的回調
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request,
$route->run($request)
);
});
}
如何獲取Route中間件的,就可以跟gatherRouteMiddlewares
,這個代碼並不難,很好跟。接下來,Request就到到達至於Controller, Request是如何到達Controller的代碼就不難了,這里就不說了
Controller后執行中間件
成功獲取Response后,在public/index.php
58行執行了$kernel->terminate($request, $response);
, 也就是在主要邏輯處理完成之后,再執行此代碼,它實際上調用是的\Illuminate\Foundation\Http\Kernel::terminate
, 跟進去就很容易發現,它處理了這此請求所涉及到的中間件,並執行了各自的terminate
方法,到這里,中間件的另一個功能就展現出來了,就是主要邏輯完成之后的收尾工作.到這里為止,中間件就完成了它的使命(一個請求也就完成了).
如何使用中間件
在官方文檔上講解的很清楚注冊中間件
中間件小結
至此,中間件的實現邏輯與使用就清晰了.從執行的順序來分,一個在Controller
之前,一個在Controller
之后,所以它一個很重要的作用就是可以讓Controller
專注於自己的主要邏輯的職責更明確. 奇怪的是,但前后兩種中間件的執行方式卻不一樣, \Illuminate\Foundation\Http\Kernel::terminate
,中間件的結束卻沒有使用Pipeline
, 而是直接foreach
.相同的工作卻用兩種代碼來實現.現在看來,中間件本身並不復雜,但它帶給了我兩個啟發,1.層次明確 2,Pipeline
所帶來的優雅.
下面是我學習過程中的一些資源
http://laravelacademy.org/post/3088.html
http://www.jianshu.com/p/3c2791a525d0