LoginSignup
9
5

More than 3 years have passed since last update.

LaravelのコアコードからMiddlewareとControllerの実行手順を理解する

Posted at

前提

Laravelでは、ミドルウェア(主にリクエスト時とかレスポンス時にフィルタリングとしての機能を書く)からコントローラー(実際のリクエストに対する処理を書く)の実行を、柔軟に変更するためにIlluminate\Pipeline\Pipelineを実装し対応している

Pipelineは、実行前に個々の実装(ここではMiddleware)した処理を構成し実行することができる
多段のクロージャでタマネギみたいな構造を構成する
これでMiddlewareのオン、オフや優先度の変更が簡単にできるようになっている

Laravelはリクエストのハンドリングの際Pipelineで、適応するミドルウェアのスタックが走るように設計されている
App\Http\Kernelでミドルウェアスタックを管理している

App\Http\Kernel
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // 全てのリクエスト時に走るミドルウェア(後の解説で使う)
    protected $middleware = [
        \App\Http\Middleware\AMiddleware::class,
        \App\Http\Middleware\BMiddleware::class,
    ];

    // ミドルウェアをグルーピングできる
    protected $middlewareGroups = [
        'session' => [
            \Illuminate\Cookie\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        ],
    ];

    // 各ルートごとに設定するミドルウェア
    // ルーティング構成時にmiddlewareオプションで設定できる
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    ];

    // ミドルウェアの優先度を設定
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    ];
}
ミドルウェアの例(上記$middlewareで設定しているMiddleware、後の解説で使う)
class AMiddleware
{
    public function handle($request, Closure $next)
    {
        Print "Aの前処理";
        $ret = $next($request);
        Print "Aの後処理";
        return $ret;
    }
}

class BMiddleware
{
    public function handle($request, Closure $next)
    {
        Print "Bの前処理";
        $ret = $next($request);
        Print "Bの後処理";
        return $ret;
    }
}

コアコードを追う

リクエストを受けレスポンスを構成するまでに、
Illuminate\Foundation\Http\KernelのsendRequestThroughRouterが実行される

Illuminate\Foundation\Http\Kernel@sendRequestThroughRouterの一部
return (new Pipeline($this->app))
    ->send($request) 
    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
    ->then($this->dispatchToRouter());

Pipelineを通して使いたいリクエストをsendメソッドで設定し、終点までに実行したい処理(ミドルウェア)をthroughメソッドで設定、それからthenによりメインの処理を引数で渡して設定する
この段階で設定されるミドルウェアは、App\Http\Kernelで設定した$middlewareの値

thenをシンプルにしたコード
function then(Closure $destination)
{
    // array_reduceでミドルウェアをイテレートする
    $pipeline = array_reduce(
        array_reverse($this->middlewares),

        // $nextClosureは一つ前のイテレーションで返されたクロージャ
        function ($nextClosure, $middlewareClass) {

            // ミドルウェアの定義時の$nextが$nextClosureに当たる
            return function ($request) use ($nextClosure, $middlewareClass) {

                // class名からミドルウェアインスタンスの取得
                $middleware = app($middlewareClass);

                // $this->methodはミドルウェアの定義時のhandleメソッド
                // ここで次の処理に$nextClosureと$requestを次に渡す
                return $middleware->{$this->method}($request, $nextClosure);
            };
        },

        // イテレーションで空を返すと$destinationが最後に設定される
        function ($request) use ($destination) {
            return $destination($request);
        }
    );

    return $pipeline($this->request);
}

上記は、thenメソッドを要約したコード、これの返り値の$pipelineは下記のようになる

$pipeline
function ($request) {
    return app(\App\Http\Middleware\AMiddleWare::class)
    ->handle($request, function ($request) {
        return app(\App\Http\Middleware\BMiddleWare::class)
        ->handle($request, function ($request) {
            return $destination($request);
        });
    });
};

App\Http\Kernelで設定した\$middlewareの処理がクロージャで階層化されて順次実行され、最下層に\$destinationが設定されるようにPipelineが構成され、以下のように実行されるようになる

"Aの前処理"
"Bの前処理"
$destination($request)
"Bの後処理"
"Aの後処理"

Middlewareごとに次のクロージャを覆う形になるのでタマネギ構造とか言われているらしい
どのクロージャも最後はレスポンスオブジェクトを返すので、最終的にレスポンスオブジェクトが返る
ここでの\$destinationは$this->dispatchToRouter()でこれを実行すると再び別のpipelineが設定される

次のpipeline(Illuminate\Routing\Router@runRouteWithinStack)
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
    return $this->prepareResponse(
        $request, $route->run()
    );
});

ここでの\$middlewareは、App\Http\Kernelで設定した\$routeMiddlewareもしくは、\$middlewareGroupsをルーティングで設定したもので、$route->run()でコントローラーが設定される
タマネギの中にタマネギが入っている感じ

ちなみに、LaravelのドキュメントではMiddlewareの前処理をBeforeMiddleware、後処理をAfterMiddlewareとかで書かれている

参考サイト

How handle() method of Laravel middleware is called using 'Clousre $next' in another Middleware?

Laravel Pipeline Interpretation

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5