ホームページ  >  記事  >  PHPフレームワーク  >  laravel5.2をベースとしたミドルウェアのソースコード解析

laravel5.2をベースとしたミドルウェアのソースコード解析

不言
不言オリジナル
2018-07-31 14:37:182187ブラウズ

laravel5.2 では、HTTP の主な機能は HTTP リクエストをフィルタリングすることです (php aritsan にはミドルウェア機構がありません)、またシステムレベル (HTTP フィルタリング層) をさらに強化します。クリアでエレガントな使い心地。ただし、ミドルウェアを実装するコードは非常に複雑なので、ミドルウェアのソースコードの内容を分析してみましょう。 php aritsan是没有中间件机制的),同时也让系统的层次(Http过滤层)更明确,使用起来也很优雅。但实现中间件的代码却很复杂,下面就来具分析下有关中间件的源码的内容。

中间件源码

中间件本身分为两种,一种是所有http的,另一种则是针对route的。一个有中间件的请求周期是:Request得先经过Http中间件,才能进行Router,再经过Requset所对应Route的Route中间件, 最后才会进入相应的Controller代码。laravel把请求分为了两种:http和console。不同的请求方式用它自己的Kernel来驱动Application。Http请求则是通过
IlluminateFoundationHttpKernel类来驱动,它定义了所有的中间件,其父类IlluminateFoundationHttpKernel::handle就是对请求进行处理的入口了

Http中间件

跟踪入口handle()方法,很容易发现该函数(IlluminateFoundationHttpKernel::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(通过方法名就知道了), 主要的逻辑则是通过IlluminateRoutingPipeline完成的, 作用就是让Requset通过Http中间件的检测,然后再到达Router。这里的代码看起来很优雅,但不是很好理解。所以,了解Pipeline的运行机制就会明白中间件的使用。

Pipeline的运行实现

Pipleline基类是IlluminatePipelinePipeline,它的执行在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
    );

laravel5.2をベースとしたミドルウェアのソースコード解析

执行说明:
1.$result_0是初始化的值 ,为$firstSlice ,即是IlluminatePipelinePipeline::getInitialSlice的返回回调
2.每遍历一个元素,都会执行IlluminatePipelinePipeline::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));
}

逻辑处理已经到了IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::handle,其代码是:

public function handle($request, Closure $next)
{
    if ($this->app->isDownForMaintenance()) {
        throw new HttpException(503);
    }

    return $next($request);
}

这里,它处理了这个中间件所需要过滤的条件,同时执行了$next($request),即IlluminateFoundationHttpKernel::dispatchToRouter(), 这样,就把Request转到了Router中,也就完成了Http中间件的所有处理工作,而$next($request)是每个中间件都不可少的操作,因为在回调中嵌套了回调,就是靠中间件把Request传递到下一个回调中,也就会解析到下一个中间件,直到最后一个。紧跟上面的已分析的Pipeline执行过程,讲其补充完整:

6.执行$result_3中的回调,getSlice实例化中间件,执行其handle,在中间件处理中执行回调

7.回调中还嵌套回调的,每个中间件中都需有执行回调的代码$next($request)

ミドルウェアのソース コード

ミドルウェア自体は 2 つのタイプに分かれており、1 つはすべて http 用で、もう 1 つはルート用です。 。ミドルウェアを使用したリクエスト サイクルは次のとおりです。リクエストはルーターに送信される前に、まず 🎜HTTP ミドルウェアを通過し、次に、 に対応するルートの🎜Route ミドルウェアを通過する必要があります。 Requset >、最後に対応する🎜コントローラー コードが入力されます。 Laravel はリクエストを http と console の 2 つのタイプに分類します。さまざまなリクエスト メソッドは、独自の Kernel を使用して Application を駆動します。 HTTP リクエストは、すべてのミドルウェアを定義する
IlluminateFoundationHttpKernel クラスによって駆動され、その親クラス IlluminateFoundationHttpKernel::handle がリクエストを処理するためのエントリ ポイントです 🎜HTTP ミドルウェア🎜エントリ handle() メソッドを追跡すると、この関数を簡単に見つけることができます ( IlluminateFoundationHttpKernel::sendRequestThroughRouter): 🎜
// 使用管道,发送$request,使之通过middleware ,再到$func
(new Pipeline($this->app))->send($request)->through($this->middleware)->then($func);
🎜この関数は Requset を Router に配布します (メソッド名でわかります)。メイン ロジックは IlluminateRoutingPipeline を通じて完了します。機能は、Requset が Http ミドルウェアの検出を通過させてから Router に到達させることです。このコードはエレガントに見えますが、あまり理解できません。したがって、Pipeline の動作メカニズムを理解すると、ミドルウェアの使用方法が理解できます。 🎜

パイプラインの実行中の実装

🎜Pipleline 基本クラスは IlluminatePipelinePipeline です。その実行は then メソッド内で行われます: 🎜
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)
                        );
                    });
}
🎜 このコード実行の意図を理解するには、array_reduce() が何をするのかを知る必要があります。 array_reduce がどのように動作するかを理解するには、まず array_reduce を書き換えます。 🎜rrreee🎜 つまり、ソース コードの $func getSlice( )、コールバック関数を返します: function($passable) use ($stack, $pipe){...}($stack and $pipe は入力された特定の値に置き換えられます)、つまり、最後の戻り結果として次の $func に入力される最初のパラメーターは上記のコールバック関数です。配列の走査が完了すると、array_reduce はコールバック関数を返します。ここで重要なのは、このコールバック関数がどのようなもので、どのように実行されるかを理解することです。議論を容易にするために、次のコードを分析できます: 🎜rrreee🎜laravel5.2をベースとしたミドルウェアのソースコード解析🎜🎜実行命令 :
1.$result_0 は初期化された値であり、$firstSlice であり、IlluminatePipelinePipeline::getInitialSliceの戻りコールバックです。 >
/>2. 要素が走査されるたびに、IlluminatePipelinePipeline::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() がどのように動作するかが明確になりました。続けて、コールバック関数がどのように実行されるかを理解する必要があります。次に、sendRequestThroughRouterPipeline に従って、その実行方法を確認します。 🎜rrreee🎜上で分析した Pipeline 実行プロセスを使用すると、最後の実行が 🎜rrreee🎜 であることがすぐに分析されます。論理処理は、次の IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::handle に到着しました。コードは次のとおりです: 🎜rrreee🎜 ここでは、このミドルウェアで必要なフィルタリング条件を処理し、$next($request)、つまり IlluminateFoundationHttpKernel::dispatchToRouter() を実行します。 >, このようにして、Request が Router に転送され、Http ミドルウェアのすべての処理作業が完了します。$next($request) は、すべてのミドルウェアにとって必須の操作です。 callback はコールバック内にネストされています。これは、ミドルウェアが Request を次のコールバックに渡し、最後のコールバックまで次のミドルウェアで解析されることを意味します。上記で分析した Pipeline 実行プロセスに従って、それを完全に説明します。 🎜🎜6. $result_3 でコールバックを実行し、getSlice でミドルウェアをインスタンス化し、その handle、ミドルウェア処理でコールバックを実行します🎜🎜7. コールバックはコールバックにもネストされており、各ミドルウェアにはコールバック $next($request) code> を実行するコードが必要です。コールバックが実行される順序は 3::handel、2::handel、1::handel、$first🎜 です。<p>8.最里面一层,一定是传递给then()的参数,then执行的就是最后一步</p> <p>9.执行的顺序是由数组中的最后一个,向前,到then()的参数,为了使其执行顺序是数组中的第一个到最后一个,再到then()中的参数,then()方法中就做了一个反转<code>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(&#39;middleware.disable&#39;) &&
                         $this->container->make(&#39;middleware.disable&#39;) === 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。