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\Route
の run()
を呼び出す。
/**
* 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の設定やコントローラーアクションに関しては次回読む予定