PHP
lumen

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

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