17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-24

概要

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

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

start

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

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

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

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

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

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

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

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()でいろいろ準備をしてるみたいです。

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()の引数にぶちこんでいきます。

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()に戻ります。

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を生成しているみたいですね。

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をプロパティにセットしています。

Pipelinethen()はmiddlewareを適用してrouterにdispatchを行います。(then()については複雑なのでこちらの記事を読んでください。LaravelのPipeline::thenでmiddlewareを処理している流れをまとめてみた)

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

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));
        }
    };
}

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

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

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

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

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()でレスポンスを返却しています。

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に処理を委譲していきます。

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()
    );
}
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されレスポンスに変換されています。

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()が呼ばれます。

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));
        }
    };
}
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;
}
17
6
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
17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?