LoginSignup
9
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-04-17

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

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7