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

  • 7
    Like
  • 0
    Comment
More than 1 year has passed since last update.

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

lumenのコンテナについて

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

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

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

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

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

    /**
     * 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とかいうメソドが用意されていて、これがかなり便利

    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では次のような利用がされている。

    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したら消える点には注意。