テーマ & 前置き
Laravel5.6のコードを読んでいきます。
一般的なWebアプリケーションフレームワークではリクエストがあると/public/index.phpが実行されるようになってます。apacheなら.htaccessの設定とかnginxのconfファイルの設定です。
ただし、我々が普段Webアプリケーションの開発をする場合、それはModel/View/Controllerを開発することとほぼ同意かとおもいます。(俺はRepositoryもつくってるとか、MVCとは限らないとかはとりあえず置いておいて。)public/index.phpを意識することなんてほぼないでしょう。
そこで、public/index.phpからはじまって我々が普段開発しているControllerの処理が実行されるまでの経緯を順に追ってみたいと思います。超めんどい(本音)
ちょっと雑な記述も多いかもしれませんが、メモ程度でも残しておけば、これから読む人の手助けになるかもしれないにゃ?と思ったので記事にしました。
Let's コードリーディング!!
重要そうなところだけ抜粋していきます。
メソッドの途中も特に不要と判断したらなんの断りもなく削除して抜粋します。
最初はpublic/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
bootstrap/app.phpでは、Applicationのインスタンスを作っている。
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
return $app;
LaravelではApplicationクラスはDIコンテナ(=サービスコンテナ)です。
ここでHttp\KernelとConsole\Kernelをコンテナに登録している。
public/index.phpの続きが以下。
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
Kernelのインスタンスを作って使っている。
ここではConsoleではなくてHttpの方、つまり、App\Http\Kernelの方を追ってみる。
Kernelクラス
handleメソッドは親クラスのApp\Http\KernelはIlluminate\Foundation\Http\Kernelにある。
public function handle($request)
{
$response = $this->sendRequestThroughRouter($request);
}
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)
のinstanceメソッドはIlluminate\Container\Containerクラスに定義されている。
既にあるインスタンスをコンテナに登録する場合に利用するメソッドで、Requestをコンテナに登録したということ。
その下ですぐにインスタンスをclearしてない?と思うかもしれないが、Facade自体が持っているインスタンスをコンテナのインスタンスに置き換えるために必要な処理。一応Facadeクラスの該当メソッドと関連をみてみる。
abstract class Facade
{
protected static $resolvedInstance;
public static function clearResolvedInstance($name)
{
unset(static::$resolvedInstance[$name]);
}
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
resolveFacadeInstanceメソッドで、$resolvedInstanceがない場合はコンテナからインスタンスを取得しているのがわかる。
Illuminate\Foundation\Http\Kernelに戻る。
$this->bootstrap();
では、アプリケーションに必要な基底クラス群をインスタンス化してコンテナに登録していっている。
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());
}
}
protected function bootstrappers()
{
return $this->bootstrappers;
}
ApplicationクラスのbootstrapWithメソッドに$bootstrappers
を渡している。
bootstrapWithメソッドをみてみる。
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
$bootstrappers
の各クラスをインスタンス化して、bootstrapメソッドを実行している。
$bootstrappers
に定義されるクラスはすべてbootstrapメソッドを持っており、その中で様々なサービスをインスタンス化し、コンテナに登録している。また、bootstraの名前が示す通り、アプリケーションの基本的な設定なども同時に行なっている。
Illuminate\Foundation\Bootstrap\LoadConfigurationを追ってみる。
public function bootstrap(Application $app)
{
$items = [];
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$loadedFromCache = true;
}
$app->instance('config', $config = new Repository($items));
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});
date_default_timezone_set($config->get('app.timezone', 'UTC'));
mb_internal_encoding('UTF-8');
}
configファイルを読み込んでコンテナに登録した後、アプリの実行環境を設定したり、タイムゾーンの設定を設定したり、内部エンコーディングをUTF-8に設定したりしている。
目的地到着
Illuminate\Foundation\Http\Kernelに戻る。最後の部分。
protected function sendRequestThroughRouter($request)
{
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
$this->router
はIlluminate\Routing\Routerオブジェクト。dispatchを確認して見る。
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)
);
}
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()
);
});
}
findRoute
は文字通り、リクエストから対応するルーティングを見つけている。
prepareResponse
はControllerのactionメソッドの戻り値をResponseオブジェクトに変換するもの。
そして、runRouteWithinStack
が今回の目的地。
ここで登録してあるミドルウェアの処理とアクセスされたルーティングの処理(=Controllerのactionメソッド)が実行される。
Pipeline周り
Pipelineの動きを紐解きつつ具体的に見るのは後日、別記事にてたぶん書く(予定は未定)
最後に
間違えあったら修正おなしゃーす。