Laravelの処理の流れコードリーディング（投げたエラーはどこで処理されている？？？）

## 概要
Laravelではthrowしたエラーを明示的にcatchしなくてもよしなにしてくれます。

エラーをカスタムしたいなと思ったときにどこでエラーをキャッチしているのか知りたかったのでLaravelの流れを読むことにしました。

## start

```php:public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
```

`Kernel::handle()`のtry句の中身を見ていきます。

```php:vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
$request->enableHttpMethodParameterOverride();

$response = $this->sendRequestThroughRouter($request);
```

`enableHttpMethodParameterOverride()`はプロパティをtrueにセットしているだけなのでスルーします。

`sendRequestThroughRouter()`は同じクラスに実装されています。

```php:vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
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());
}
```

`$this->app->instance('request', $request)`では`$request`をサービスコンテナに登録しています。

`Facade::clearResolvedInstance('request')`はファサードにキャッシュされているrequestをクリアしています。（キャッシュされるタイミングはあるのだろうか？）

`$this->bootstrap()`でいろいろ準備をしてるみたいです。

```php:vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
protected $bootstrappers = [
    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    \Illuminate\Foundation\Bootstrap\BootProviders::class,
];

public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}
```

`$bootstrappers`プロパティの配列を`bootstrapWith()`の引数にぶちこんでいきます。

```php:vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function bootstrapWith(array $bootstrappers)
{
    foreach ($bootstrappers as $bootstrapper) {

        $this->make($bootstrapper)->bootstrap($this);

    }
}
```

サービスコンテナで順番にインスタンス化していって、その中の`bootstrap()`を呼んでいます。

`bootstrap()`が終わったら、`sendRequestThroughRouter()`に戻ります。

```php:vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
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());
}
```

ここでreturnしているので、`Pipeline`クラスがresponseを生成しているみたいですね。

```php:vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
public function send($passable)
{
    $this->passable = $passable;

    return $this;
}

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();

    return $this;
}
```

`send()`はrequestをプロパティにセットして、`through()`はmiddlewareをプロパティにセットしています。

`Pipeline`の`then()`はmiddlewareを適用してrouterにdispatchを行います。(`then()`については複雑なのでこちらの記事を読んでください。[LaravelのPipeline::thenでmiddlewareを処理している流れをまとめてみた](https://qiita.com/wim/items/869bf668b98b37a6e2fc))

`return $pipeline($this->passable)`は最終的に`prepareDestination()`を呼んで、引数のClosureを呼び出しています。

```php:vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
protected function prepareDestination(Closure $destination)
{
    return function ($passable) use ($destination) {
        try {
            // ここが呼び出される
            return $destination($passable);
        } catch (Exception $e) {
            return $this->handleException($passable, $e);
        } catch (Throwable $e) {
            return $this->handleException($passable, new FatalThrowableError($e));
        }
    };
}
```

`$destination`は`then()`の引数に渡されたものなので、`$this->dispatchToRouter()`で生成したClosureが呼び出されます。

```php:\Illuminate\Foundation\Http\Kernel.php
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}
```

routerの`dispatch()`を呼び出します。

```php:vendor/laravel/framework/src/Illuminate/Routing/Router.php
public function dispatch(Request $request)
{
    $this->currentRequest = $request;

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

public function dispatchToRoute(Request $request)
{
    return $this->runRoute($request, $this->findRoute($request));
}

protected function runRoute(Request $request, Route $route)
{
    //...

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

`$this->findRoute()`でリクエストされたルートやメソッド（get, postなど）で対象のルーティングを見つけてきます。

`runRoute()`でレスポンスを返却しています。

```php:vendor/laravel/framework/src/Illuminate/Routing/Router.php
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(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}
```

Pipelineでmiddlewareを適用しつつ、`$route->run()`でいよいよControllerに処理を委譲していきます。


```php:vendor/laravel/framework/src/Illuminate/Routing/Route.php
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();
    }
}

protected function runController()
{
    return $this->controllerDispatcher()->dispatch(
        $this, $this->getController(), $this->getControllerMethod()
    );
}
```

```php:vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php
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));
}
```

ここまできてやっと`$controller->{$method}`とコントローラーのメソッドが呼ばれました！

コントローラーで投げられた例外は、`HttpResponseException`クラスなら、`Route::run()`ないでcatchされレスポンスに変換されています。

```php:vendor/laravel/framework/src/Illuminate/Routing/Route.php
public function run()
{
    try {
        if ($this->isControllerAction()) {
            return $this->runController();
        }
    } catch (HttpResponseException $e) {
        return $e->getResponse();
    }
}
```

それ以外の例外は、`Pipeline::prepareDestination()`でキャッチされ、`handleException()`が呼ばれます。

```php:vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
protected function prepareDestination(Closure $destination)
{
    return function ($passable) use ($destination) {
        try {
            return $destination($passable);
        } catch (Exception $e) {
            return $this->handleException($passable, $e);
        } catch (Throwable $e) {
            return $this->handleException($passable, new FatalThrowableError($e));
        }
    };
}
```


```php:vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:54
protected function handleException($passable, Exception $e)
{
    if (! $this->container->bound(ExceptionHandler::class) ||
        ! $passable instanceof Request) {
        throw $e;
    }

    $handler = $this->container->make(ExceptionHandler::class);

    $handler->report($e);

    $response = $handler->render($passable, $e);

    if (method_exists($response, 'withException')) {
        $response->withException($e);
    }

    return $response;
}
```