laravel リクエストが送られコントローラーが実行されるまでの旅路

# 概要

- laravelアプリケーションに何かしらのリクエストが送られコントローラーの処理が実行されるまでの旅路を追ってみた。

# 旅路

1. public/index.phpが実行され初期段階の処理が実行される。（autoloadやら）
1. 下記のように`$kernel->handle()`が実行される。

    ```public/index.php
    $app = require_once __DIR__.'/../bootstrap/app.php';
    
    $kernel = $app->make(Kernel::class);
    
    $response = $kernel->handle(
        $request = Request::capture()
    )->send();
    ```

1. handle()はインターフェースの先に実装がある。
    - インターフェース `vendor/laravel/framework/src/Illuminate/Contracts/Http/Kernel.php`
    - 実装 `vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php`
    - 実装しているクラスを継承しているクラス `app/Http/Kernel.php`
1. handle()関数の実装部分で下記が実行されている。
   
    ```vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
    $response = $this->sendRequestThroughRouter($request);
    ```

1. 同クラス内のsendRequestThroughRouter()関数では下記のような処理が実行されている。

    ```vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
    ```

1. 上記メソッドチェーンのthen()の引数として`$this->dispatchToRouter()`が実行され、当該関数の中で下記が実行されている。

    ```vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
    return $this->router->dispatch($request);
    ```

1. 上記処理でdispatch()関数が実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Router.php
    return $this->dispatchToRoute($request);
    ```

1. 上記処理でdispatchToRoute（）関数が実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Router.php
    return $this->runRoute($request, $this->findRoute($request));
    ```

1. 上記処理でrunRoute()関数の第二引数の値としてfindRoute()関数が実行される。ここではルート情報をfindして返している。

    ```vendor/laravel/framework/src/Illuminate/Routing/Router.php
    /**
     * Find the route matching a given request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Routing\Route
     */
    protected function findRoute($request)
    {
        $this->events->dispatch(new Routing($request)); // イベントリスナーの呼び出し？

        $this->current = $route = $this->routes->match($request); // リクエスト情報から

        $route->setContainer($this->container);

        $this->container->instance(Route::class, $route);

        return $route;
    }
    ```

1. runRoute()関数が実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Router.php
    /**
     * Return the response for the given route.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Routing\Route  $route
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(fn () => $route);

        $this->events->dispatch(new RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }
    ```

1. 上記処理のprepareResponse()関数の第二引数でrunRouteWithinStack()関数が実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Router.php
    /**
     * Run the given route within a Stack "onion" instance.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                                $this->container->make('middleware.disable') === true;

        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

        return (new Pipeline($this->container))
                        ->send($request)
                        ->through($middleware)
                        ->then(fn ($request) => $this->prepareResponse(
                            $request, $route->run()
                        ));
    }
    ```

1. 上記の処理でprepareResponse()関数の第二引数として$route->run()が実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Route.php
    /**
     * Run the route action and return the response.
     *
     * @return mixed
     */
    public function run()
    {
        $this->container = $this->container ?: new Container;

        try {
            if ($this->isControllerAction()) {
                return $this->runController();
            }

            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }
    ```

1. 上記の処理でisControllerAction()関数が実行されルーティング先がコントローラーであるかどうかチェックされる。コントローラーだった場合runController()関数が実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Route.php
    /**
     * Run the route action and return the response.
     *
     * @return mixed
     *
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    protected function runController()
    {
        return $this->controllerDispatcher()->dispatch(
            $this, $this->getController(), $this->getControllerMethod()
        );
    }
    ```

1. 上記のdispatch()関数は第一引数にルート情報、第二引数にコントローラー、第三引数にコントローラーの関数名を受け取り下記下記を実行する。
    - インターフェース `vendor/laravel/framework/src/Illuminate/Routing/Contracts/ControllerDispatcher.php`
    - 実装 `vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php`

    ```vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php
    /**
     * Dispatch a request to a given controller and method.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @param  mixed  $controller
     * @param  string  $method
     * @return mixed
     */
    public function dispatch(Route $route, $controller, $method)
    {
        $parameters = $this->resolveClassMethodDependencies(
            $route->parametersWithoutNulls(), $controller, $method
        );

        if (method_exists($controller, 'callAction')) {
            return $controller->callAction($method, $parameters);
        }

        return $controller->{$method}(...array_values($parameters));
    }
    ```

1. 上記の`method_exists($controller, 'callAction')`はユーザーが定義したコントローラーが`App\Http\Controllers\Controller`を継承している場合、更にそのControllerクラスが`Illuminate\Routing\Controller`を継承しているはずなのでtrueになる。
1. callAction()関数が各引数を用いて実行される。こちらでコントローラーの関数が指定され引数を用いて実行されている。例えばコントローラーのfoo関数が実行される場合`$this->{$method}(...array_values($parameters))`が`$this->foo([])`となり実行される。

    ```vendor/laravel/framework/src/Illuminate/Routing/Controller.php
    /**
     * Execute an action on the controller.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function callAction($method, $parameters)
    {
        return $this->{$method}(...array_values($parameters));
    }
    ```

# 参考文献

- [https://maasaablog.com/development/laravel/818/#toc9](https://maasaablog.com/development/laravel/818/#toc9)