lumen ガイド 2 コンテナの仕組み解剖


laravel謹製のマイクロフレームワーク `lumen`が出たので使ってみた。
ざっくり触りながらのまとめになりますが、ツッコミポイントとかアレば是非コメントお願いしまっす。

## lumenのコンテナについて

`Lumen\Application`もlaravelと同じくコンテナの拡張になっていて。ここは共通部分の`Illuminate\Container\Container`になってる。

だから基本的なコンテナ周りの考え方はlaravelの仕組みを継承できる感じです。

## デフォルトコンテナどうなってるの問題

lumenにはProviderとかのデフォルト登録に関する仕組みがない。この辺どうなってのんのさ、みたいな疑問はまぁわく所だと思う。

`\Laravel\Lumen\Application`を覗いてみると…


````PHP
    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array   $parameters
     * @return mixed
     */
    public function make($abstract, $parameters = [])
    {
        if (array_key_exists($abstract, $this->availableBindings) &&
            ! array_key_exists($this->availableBindings[$abstract], $this->ranServiceBinders)) {
            $this->{$method = $this->availableBindings[$abstract]}();

            $this->ranServiceBinders[$method] = true;
        }

        return parent::make($abstract, $parameters);
    }
````

このへんがミソっぽい。どーやらmakeをゴニョゴニョして上手いことやってるみたいだ。

まず、`$availabelBindings`の辺りになるけど、くっそ長いがこんなかんじの記述がcoreの中に書かれてる。

````
    /**
     * The available container bindings and their respective load methods.
     *
     * @var array
     */
    public $availableBindings = [
        'auth' => 'registerAuthBindings',
        'Illuminate\Contracts\Auth\Guard' => 'registerAuthBindings',
        'auth.password' => 'registerAuthBindings',
        'Illuminate\Contracts\Auth\PasswordBroker' => 'registerAuthBindings',
        'Illuminate\Contracts\Bus\Dispatcher' => 'registerBusBindings',
        'cache' => 'registerCacheBindings',
        'Illuminate\Contracts\Cache\Factory' => 'registerCacheBindings',
        'Illuminate\Contracts\Cache\Repository' => 'registerCacheBindings',
        'config' => 'registerConfigBindings',
        'composer' => 'registerComposerBindings',
        'cookie' => 'registerCookieBindings',
        'Illuminate\Contracts\Cookie\Factory' => 'registerCookieBindings',
        'Illuminate\Contracts\Cookie\QueueingFactory' => 'registerCookieBindings',
        'db' => 'registerDatabaseBindings',
        'encrypter' => 'registerEncrypterBindings',
        'Illuminate\Contracts\Encryption\Encrypter' => 'registerEncrypterBindings',
        'events' => 'registerEventBindings',
        'Illuminate\Contracts\Events\Dispatcher' => 'registerEventBindings',
        'Illuminate\Contracts\Debug\ExceptionHandler' => 'registerErrorBindings',
        'files' => 'registerFilesBindings',
        'filesystem' => 'registerFilesBindings',
        'Illuminate\Contracts\Filesystem\Factory' => 'registerFilesBindings',
        'hash' => 'registerHashBindings',
        'Illuminate\Contracts\Hashing\Hasher' => 'registerHashBindings',
        'Psr\Log\LoggerInterface' => 'registerLogBindings',
        'mailer' => 'registerMailBindings',
        'Illuminate\Contracts\Mail\Mailer' => 'registerMailBindings',
        'queue' => 'registerQueueBindings',
        'queue.connection' => 'registerQueueBindings',
        'Illuminate\Contracts\Queue\Queue' => 'registerQueueBindings',
        'request' => 'registerRequestBindings',
        'Illuminate\Http\Request' => 'registerRequestBindings',
        'session' => 'registerSessionBindings',
        'session.store' => 'registerSessionBindings',
        'Illuminate\Session\SessionManager' => 'registerSessionBindings',
        'translator' => 'registerTranslationBindings',
        'url' => 'registerUrlGeneratorBindings',
        'validator' => 'registerValidatorBindings',
        'view' => 'registerViewBindings',
        'Illuminate\Contracts\View\Factory' => 'registerViewBindings',
    ];

````

publicなんで継承可。適当に触っても大丈夫っぽい(タイミングはしっかり考えなくちゃならんけど)。

### lumenのmakeは一味ちがうぞ

lumenのmakeは一味ちがう。`availableBindings`というマップがあって、ここに登録された名前(`$abstract`)のコンテナコールに対し、事前処理を挟むことができる。事前処理は処理時点で処理フラグが立ち一度しかコールされない。
(ただし各呼出名に対して一度、という点には注意。コンストラクタ注入と、一般的コールでクラス名と簡易名で同じメソドを登録:上記registerViewBindingsみたいな、場合にはそれぞれのコール形式でregisterViewBindingsが実行されるので、singletonなどの工夫が必要)

つまりオンデマンドでコンテナ登録処理を記述できる。

`availableBindings`はコンテナ呼び出し名=>処理するべきメソドの形式になっていて、事前処理はApplicationクラス側に実装しなきゃいけないけど、lumen使うケースではsilexとかと同じくやっぱり継承はしとくべきだと思う。

ちなみにApplicationクラスにはprotectedで`loadComponent`とかいうメソドが用意されていて、これがかなり便利

````PHP
    protected function loadComponent($config, $providers, $return = null)
    {
        $this->configure($config);

        foreach ((array) $providers as $provider) {
            $this->register($provider);
        }

        return $this->make($return ?: $config);
    }
````

第一引数で指定した名称でconfigファイルを読み込み、第二引数でサービスプロバイダを登録、第三引数でコンテナからデータを取り出す。

例えば`view`コンポーネント周りの登録を行う`registerViewBindings`では次のような利用がされている。

````PHP
    protected function registerViewBindings()
    {
        $this->singleton('view', function () {
            return $this->loadComponent('view', 'Illuminate\View\ViewServiceProvider');
        });
    }

````

基本的なコンテナの使い方としてサービスプロバイダで登録して、クライアントがコンテナから取り出す、みたいな形式になると思うけど、
このmakeシステムのイイ点としては、本当に必要になるまでサービスプロバイダを実行する必要がない点。必要なときに必要な分だけのサービスプロバイダを実行できる。
ちゃんと`make`側で処理がまとめられているので、サービスプロバイダの実行漏れも無い。

### withFacadesの挙動

コアのwithFacadesメソドに記述のある部分のみclass_aliasを呼んでる。
グローバルに影響する部分なのでstatic変数を使って一回しかコールされないよう調整が付いている点にも注意。

このへんは配列で管理とかないので、必要がアレば自前でメソド継承して、if文とか親クラスの中身確認しながら、実装するといいと思います。
(リストを配列化して別メソドに分けたりとか…そういうのあると便利なんだけどなぁ…)

## コンテナからの取り出し

コンテナからの取り出しには`app()`関数が用意されていてそこからグローバルにできる。

起動しているアプリケーションコンテナは`Container::getInstance()`で取得する事が可能で、`app()`もこの挙動に依存している。

`app('view')`みたいな感じでコンテナから取り出し(make)することが可能だし、デフォルト登録のサービスとかは`config()`とか`view()`みたいな専用のヘルパーも用意されてたりする。

`Container::getInstance()`の仕組み自体は単純なstatic変数によるプールで、`Lumen\Application`では、コンストラクタ実行時点で生成インスタンスがstaticフィールドに記録される。
staticフィールド自体は`Illuminate\Container\Container`なので他の`Illuminate\Container\Container`経由インスタンスで`setInstance`したら消える点には注意。