確か自分の記憶だとbootのほうが先に呼ばれてregisterが後に呼ばれるんだっけ・・・?
色々と真相が明らかになっていく様子を記事にしました。
今回確認に使用したLaravelのバージョン
多分5.0.22です。
先にソース読んでからXdebugでリモートデバッグしたので、曖昧に書かれてる箇所多いです。
一応勘違いしてる部分は修正しましたが、間違いがあるかも・・・w
解説
public/index.phpにリクエストが来る
boostrap/app.phpにて
singletonでHTTPKernelをコンテナに追加する
$app->singleton(
'Illuminate\Contracts\Http\Kernel',
'App\Http\Kernel'
);
呼び出しているのはこれ、singletonメソッド
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
bindメソッドは何をやってるのかよく分かってないが、使ってる所だけ確認する
public function bind($abstract, $concrete = null, $shared = false)
{
// arrayで入ってないので通らない
if (is_array($abstract))
{
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);
}
// $this->instancesと$this->aliasesに$abstructがkeyで登録されてたらunsetしてる。
$this->dropStaleInstances($abstract);
// nullではないので通らない
if (is_null($concrete))
{
$concrete = $abstract;
}
// クロージャーではないのでここ通る
if ( ! $concrete instanceof Closure)
{
// Containerのmakeメソッドが呼ばれる、クロージャーが返ってくる
$concrete = $this->getClosure($abstract, $concrete);
}
// ['concrete' => function($c, $parameters = []){return $c->make('App\Http\Kernel')}, 'shared' => true]が
// $this->bindings['Illuminate\Contracts\Http\Kernel']に入る。バインディングされる。
$this->bindings[$abstract] = compact('concrete', 'shared');
// $this->resolvedまたは$this->instancesのkeyに$abstractが登録されてたら通るみたいだけど、
// 今回は通らないよね
if ($this->resolved($abstract))
{
$this->rebound($abstract);
}
}
どうやらbindメソッド自体は基本登録しかやってないっぽい。ゴリゴリなんか動いてるんかなって思ってた。
それで、public/index.phpに戻る
// IoCコンテナの機能で、実際は\App\Http\Kernelのインスタンスが作られる。
$kernel = $app->make('Illuminate\Contracts\Http\Kernel');
// handleメソッド呼ばれる
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
App\Http\Kernelには対象のメソッドは無かったので、それの元のIlluminate\Foundation\Http\Kernelを調べてみる
あった、handle。とりあえず関係ないやつはコメントアウトで見やすくした。
public function handle($request)
{
// try
// {
$response = $this->sendRequestThroughRouter($request);
// }
// catch (Exception $e)
// {
// $this->reportException($e);
// $response = $this->renderException($request, $e);
// }
$this->app['events']->fire('kernel.handled', [$request, $response]);
return $response;
}
sendRequestThroughRouterメソッド見る。
なんかリクエストの処理してるみたいなんだけど、重要だったのはbootstrapメソッド。
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->middleware)
->then($this->dispatchToRouter());
}
Illuminate\Foundation\Http\Kernelのbootstrapメソッド
public function bootstrap()
{
// bootstrap済みか確認
if ( ! $this->app->hasBeenBootstrapped())
{
// 対象のbootstrapメソッドを実行する
// なお、Eventが挟める模様。前で発火するなら「bootstrapping: クラス名」、後で発火なら「bootstrapped: クラス名」
$this->app->bootstrapWith($this->bootstrappers());
}
}
なお、$this->bootstrappersは以下
protected $bootstrappers = [
// .envファイルの読み込み
'Illuminate\Foundation\Bootstrap\DetectEnvironment',
// config情報の取得。タイムゾーンセットとか、UTF-8エンコード指定してる
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
// logのインスタンス登録したりしてる。
'Illuminate\Foundation\Bootstrap\ConfigureLogging',
// Exceptionの登録。エラー関係はこれ通るんかな
'Illuminate\Foundation\Bootstrap\HandleExceptions',
// app.aliasesに書いてあるFacadeのインスタンスが作られてコンテナのaliasesに登録されます
'Illuminate\Foundation\Bootstrap\RegisterFacades',
// [1]
'Illuminate\Foundation\Bootstrap\RegisterProviders',
// [2]
'Illuminate\Foundation\Bootstrap\BootProviders',
];
[1]Illuminate\Foundation\Bootstrap\RegisterProviders
ここにある、[1]のIlluminate\Foundation\Bootstrap\RegisterProvidersを見ていった所、大抵の場合、registerメソッドのほうが早く呼ばれているっぽいことがわかった。
1.Illuminate\Foundation\Bootstrap\RegisterProvidersのbootstrapメソッドが呼ばれる
2.app->registerConfiguredProvidersが呼ばれる
3.下記で、ProviderRepositoryに登録して、loadしてる。
(new ProviderRepository($this, new Filesystem, $manifestPath))
->load($this->config['app.providers']);
4.ProviderRepositoryはmanifestファイルがあったらそれ使って、無かったら作る
5.manifestには「deferred」「when」「eager(デフォルト)」がある。
6.eagerの場合providerが作られ、registerメソッドに渡る
7.provider->registerメソッド呼ばれてる
8.boot済だったらprovider->bootメソッドも呼ぶ(今回はboot済ではないので呼ばれない。遅延ロード用でしょう)
衝撃の事実。bootメソッドのほうが早く呼ばれているのかと思っていたんですが、registerメソッドのほうが早く呼ばれているのか。勘違いしてました。
これには一部例外もあって、deferredとwhen(一部)がそうだと思う。遅延系は後で呼ばれるっぽいけど、これもregisterのほうが先に呼ばれる模様。
[2]Illuminate\Foundation\Bootstrap\BootProviders
最後にあるIlluminate\Foundation\Bootstrap\BootProvidersね。
これはapp->bootメソッドが呼ばれる。
public function boot()
{
if ($this->booted) return;
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}
$this->fireCallbacksはちょっと分からなかったが、array_walkでserviceProvidersをbootProviderメソッドに通している。
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot'))
{
return $this->call([$provider, 'boot']);
}
}
これで、bootメソッドがここで呼ばれていることが判明。長かった。
serviceproviderで遅延登録したものに関して
loadDeferredProviderっていうメソッドでbootingCallbacksメンバ変数に登録がされる。
それ以上はちょっと今回は調べていない。
まとめ
今回わかったこととしては、単純にregisterが先に呼ばれてbootは後に呼ばれるということだけだった。
多くのServiceProviderはbootメソッドだけがほとんどだった。
registerメソッドの役割としては、他のServiceProviderがregisterされていないと困るとか、IoCコンテナに登録されていないと困るときに使ってるみたい。上書きも出来るから、他で追加されていたIoCコンテナに注入したりとか出来そうですね。
ただ、大抵はbootメソッドで事足りるみたいです。
コメント