在laravel5.2中,Http的主要功能就是過濾Http請求(php aritsan
是沒有中間件機制的),同時也讓系統的層次(Http過濾層)更明確,使用起來也很優雅。但實現中間件的程式碼卻很複雜,下面就來具分析下有關中間件的源碼的內容。
中間件本身分為兩種,一種是所有http的,另一種則是針對route的。一個有中間件的請求週期是:Request得先經過Http中間件#,才能進行Router,再經過Requset所對應Route的Route中間件, 最後才會進入對應的Controller程式碼。 laravel把請求分為了兩種:http和console。不同的請求方式用它自己的Kernel
來驅動Application
。 Http請求則是透過\Illuminate\Foundation\Http\Kernel
類別來驅動,它定義了所有的中間件,其父類別\Illuminate\Foundation\Http\Kernel:: handle
就是對請求進行處理的入口了
追蹤入口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
的運作機制就會明白中間件的使用。
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執行過程,講其補充完整:
getSlice實例化中間件,執行其
handle,在中間件處理中執行回呼
$next( $request) ,才能確保回呼中的回呼會執行,執行的順序就是3::handel,2::handel,1::handel,$first
8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步
9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转array_reverse
现在,Pipeline的所有执行流程就都分析完了。实现代码真的很绕,但理解之后编写自定义的中间件应该就很容易了。现在再把Pipeline
的使用翻译成汉语,应该是这样的
// 使用管道,发送$request,使之通过middleware ,再到$func (new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
这样的代码不管是从语义上,还是使用上都很优雅,高!确实是高!再回到源码,Requset的流程就通过dispatchToRouter
进入到了Router
在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的代码就不难了,这里就不说了
成功获取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
所带来的优雅.
相关推荐:
Laravel 5.1框架中如何创建自定义Artisan控制台命令
以上是基於laravel5.2進行中間件源碼的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!