2022-05-28

[Laravel読書録]その2: $kernel->handle()

Laravelの $kernel->handle() は何をしているのか調べてみたメモ。 Laravelのバージョンは v9.8.1 です

$kernel は \App\Http\Kernel のインスタンスです。

handle() はこんな感じ。$requestは Illuminate\Http\Request のインスタンスが入る。

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Throwable $e) {
        $this->reportException($e);

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

    $this->app['events']->dispatch(
        new RequestHandled($request, $response)
    );

    return $response;
}

sendRequestThroughRouter() では $request をapp(Illuminate\Foundation\Application)のinstancesにセット、 bootstrap() を呼び出しPipelineを使ってRouterを呼び出す。

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

bootstrap()bootstrappers() を引数に app->bootstrapWith() を呼び出す。

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

bootstrapWith() はbootstrappersをmakeでインスタンス化して bootstrap() を呼び出す。

public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

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

        $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
    }
}

https://github.com/laravel/framework/blob/v9.8.1/src/Illuminate/Foundation/Application.php#L239

Pipelineの各メソッドはこんな感じ

/**
 * Set the object being sent through the pipeline.
 *
 * @param  mixed  $passable
 * @return $this
 */
public function send($passable)
{
    $this->passable = $passable;

    return $this;
}


/**
 * Set the array of pipes.
 *
 * @param  array|mixed  $pipes
 * @return $this
 */
public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();

    return $this;
}

/**
 * Run the pipeline with a final destination callback.
 *
 * @param  \Closure  $destination
 * @return mixed
 */
public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
    );

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

/**
 * Get the final piece of the Closure onion.
 *
 * @param  \Closure  $destination
 * @return \Closure
 */
protected function prepareDestination(Closure $destination)
{
    return function ($passable) use ($destination) {
        try {
            return $destination($passable);
        } catch (Throwable $e) {
            return $this->handleException($passable, $e);
        }
    };
}

やっていることとしてはミドルウェア噛ませて最終的にdispatchToRouterを呼び出している。 ここでは、共通処理としてKernelの $middleware に設定されたものが呼び出されることになる。

dispatchToRouter() はrouterの dispatch() を呼び出す

/**
 * Get the route dispatcher callback.
 *
 * @return \Closure
 */
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

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

routerは Illuminate\Routing\Router のインスタンス。 dispatch()dispatchToRoute() 経由で runRoute() を呼び出す。

/**
 * Dispatch the request to the application.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Symfony\Component\HttpFoundation\Response
 */
public function dispatch(Request $request)
{
    $this->currentRequest = $request;

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

/**
 * Dispatch the request to a route and return the response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Symfony\Component\HttpFoundation\Response
 */
public function dispatchToRoute(Request $request)
{
    return $this->runRoute($request, $this->findRoute($request));
}

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

findRoute() ではリクエストに応じた \Illuminate\Routing\Route オブジェクトを取得している。

/**
 * 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;
}

runRoute() では runRouteWithinStack() を呼び出す。 runRouteWithinStack() ではPipelineでミドルウェアを噛ませて \Illuminate\Routing\Routerun() を呼び出す。

/**
 * 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()
                    ));
}

ここで噛ませるミドルウェアはRoute固有のミドルウェアになる。

run() はこんな感じでコントローラのアクションであればそれを呼び出し、そうでなければ runCallable() を呼び出す。

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

runCallableが呼ばれるケースとしては↓こんな感じな指定をしたとき

Route::get('/', function () {
    return view('welcome');
});

runCallable() はこんな感じで $callable のClosureを呼び出す。

protected function runCallable()
{
    $callable = $this->action['uses'];

    if ($this->isSerializedClosure()) {
        $callable = unserialize($this->action['uses'])->getClosure();
    }

    return $callable(...array_values($this->resolveMethodDependencies(
        $this->parametersWithoutNulls(), new ReflectionFunction($callable)
    )));
}

まとめ

$kernel->handle() ではbootstrapを呼び出しつつ、Routerやミドルウェアを使ってリクエストのハンドリングを行う。

Routerの設定やコントローラーアクションに関しては次回読む予定

このエントリーをはてなブックマークに追加