首頁 > php框架 > Laravel > 基於laravel5.2進行中間件源碼的解析

基於laravel5.2進行中間件源碼的解析

不言
發布: 2018-07-31 14:37:18
原創
2205 人瀏覽過

在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就是對請求進行處理的入口了

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
    );
登入後複製

基於laravel5.2進行中間件源碼的解析

執行說明:
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所带来的优雅.

相关推荐:

laravel5.4中自定义包开发的实例

Laravel 5.1框架中如何创建自定义Artisan控制台命令

laravel框架的启动过程分析

以上是基於laravel5.2進行中間件源碼的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板