laravel中間件源碼分析


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;
}

所以,源代碼中的$funcgetSlice(),它返回的是一個回調函數: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.php58行執行了$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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM