Laravel 5 のタマネギ構造実装処理

Laravel

前回の Laravel 5 の処理の流れ でも書きましたが、Laravel 5 はタマネギのような構造になっており、複数のMiddlewareによって内部の処理が包まれています。このタマネギ構造をどうやって実装しているかについても少し調べていたのでメモしておくことにしました。Middlewareには、「Global Middleware」と「ルートに割り当てられたMiddleware」の2種類あるのですが、今回は Global Middleware の処理を取り上げます。

バージョン

Laravel Framework version 5.0.6

Global Middleware の実装処理

主な関連処理

\Illuminate\Foundation\Http\Kernel::handle

  • public/index.php 内から実行されます。
  • このメソッドでは、リクエストオブジェクトの取得 → Bootstrap 処理 → タマネギ構造の実行を行い、最終的にレスポンスオブジェクトが返されます。
/**
 * Handle an incoming HTTP request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function handle($request)
{
    try
    {
        return $this->sendRequestThroughRouter($request);
    }
    catch (Exception $e)
    {
        $this->reportException($e);

        return $this->renderException($request, $e);
    }
}

この中から以下が呼ばれます。

\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter

/**
 * Send the given request through the middleware / router.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->middleware)
                ->then($this->dispatchToRouter());
}

\Illuminate\Pipeline\Pipelineクラスを使ってメソッドチェーンしていますが、ここがタマネギ構造の実装になります。最後には以下のthenメソッドが呼ばれます。

\Illuminate\Pipeline\Pipeline::then

/**
 * Run the pipeline with a final destination callback.
 *
 * @param  \Closure  $destination
 * @return mixed
 */
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
    );
}

引数で渡される$destinationは、\Illuminate\Routing\Routerクラスのdispatchメソッド実行を包んだクロージャです。

この中の getSlice()メソッドの定義が以下です。

\Illuminate\Pipeline\Pipeline::getSlice

/**
 * Get a Closure that represents a slice of the application onion.
 *
 * @return \Closure
 */
protected function getSlice()
{
    return function($stack, $pipe)
    {
        return function($passable) use ($stack, $pipe)
        {
            // If the pipe is an instance of a Closure, we will just call it directly but
            // otherwise we'll resolve the pipes out of the container and call it with
            // the appropriate method and arguments, returning the results back out.
            if ($pipe instanceof Closure)
            {
                return call_user_func($pipe, $passable, $stack);
            }
            else
            {
                return $this->container->make($pipe)
                    ->{$this->method}($passable, $stack);
            }
        };
    };
}

上記の一番内側にあるクロージャが、Middlewareのhandleメソッド第二引数となるクロージャ(Closure $next)になります。このクロージャはリクエストオブジェクトを受け取り、レスポンスオブジェクトを返します。以後、このシグネチャを持ったクロージャのことを「$nextクロージャ」と呼びます。

処理の流れ

以下に、タマネギ構造のメイン処理が行われている \Illuminate\Pipeline\Pipeline::then メソッドについて説明します。(※ Global Middlewareがn個登録されているとします。)

  1. $firstSlice = $this->getInitialSlice($destination);
    • Router の dispatch メソッドを実行するクロージャを引数で受け取り、これを更にクロージャで包んで $nextクロージャの形にします。
  2. $pipes = array_reverse($this->pipes);
    • 配列として登録されている Global Middleware を逆順にします。
  3. $this->getSlice()
    • これは、$nextクロージャとMiddleware1つを受け取って、新たな $nextクロージャを生成するためのクロージャが返ってきます。これが、array_reduceの第二引数であるコールバック関数となります。
    • array_reduceの第二引数に渡すコールバックはクロージャではなく、[‘クラス名’, ‘メソッド名’] という配列でもよいので、現在のgetSlice()内の第一階層目のクロージャを getSlice()そのものにして、array_reduceの第二引数に [ __CLASS__, ‘getSlice’] を指定しても動くと思います。ただ、ひょっとするとこの場合は何か設計の柔軟性を壊すのかもしれません。
  4. array_reduce($pipes, $this->getSlice(), $firstSlice)
    • 1回目の実行:1で生成された$nextクロージャと、1つ目のMiddleware(実際は最後に登録されていたMiddleware)を基にして、新たな$nextクロージャを生成します。
    • 2回目の実行:1回目で生成した$nextクロージャと、2つ目のMiddlewareを基にして、新たな$nextクロージャを生成します。
    • :
    • n回目の実行:(n-1)回目で生成した$nextクロージャと、最後のMiddleware(実際は1つ目に登録されていたMiddleware)を基にして、新たな$nextクロージャを生成します。
    • 最終的に、array_reduce は1つの$nextクロージャを返します。これは、次のような関数になっています。
      • 引数:リクエストオブジェクト
      • 内容:本来1つ目として登録されていたMiddlewareのhandleメソッド実行する。引数は以下。
        • 第一引数:リクエストオブジェクト
        • 第二引数:次に実行すべき$nextクロージャ
  5. return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable );
    • 4で生成した$nextクロージャにリクエストオブジェクトを渡して実行します。
    • 以下のように実行されます。
      • 最初の$nextクロージャを実行(1段目)
        • 1つ目のMiddleware::handle($request, Closure $next)を実行
          • 前処理
          • 次の$nextクロージャを実行(2段目)
            • 2つ目のMiddleware::handle($request, Closure $next)を実行
              • 前処理
              • 次の$nextクロージャを実行(3段目)
                • 3つ目のMiddleware::handle($request, Closure $next)を実行
                  • 前処理
                  • 次の$nextクロージャを実行(4段目)
                    • 4つ目のMiddleware::handle($request, Closure $next)を実行
                  • 後処理
                  • レスポンスオブジェクトを返す。
              • 後処理
              • レスポンスオブジェクトを返す。
          • 後処理
          • レスポンスオブジェクトを返す。
      • レスポンスオブジェクトを返す。
    • この一番深い階層の部分で コントローラーの処理が実行され、生成されたレスポンスオブジェクトが戻り値となります(実際はViewオブジェクトが返され、Responseオブジェクトに変換される)。
    • どのクロージャも最後はレスポンスオブジェクトを返すので、最終的にレスポンスオブジェクトが返ってきます。
  6. 最終的に、このレスポンスオブジェクトが、最初にあった \Illuminate\Foundation\Http\Kernel::handleメソッドの戻り値になります。この直後に、クライアントにレスポンスが出力されます。

ちなみに今回説明した Global Middleware の中心部には、「ルートに割り当てられたMiddleware」の処理が含まれており、こちらもまた Pipeline クラスを使ったタマネギ構造になっていて、その中心部にはコントローラの処理が入っています。つまりタマネギの皮が更に重ねられている構造になっています。見通しの良い設計になっていると思いますが、いかがでしょうか。

最終更新日: 2015-2-25

Pocket

One thought on “Laravel 5 のタマネギ構造実装処理

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*