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