LaravelのServiceProviderで使われる、bootとregisterってどこで呼ばれてるの?

スポンサーリンク

確か自分の記憶だと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メソッドで事足りるみたいです。

コメント

タイトルとURLをコピーしました