はじめに
BEAR.Sunday Advent Calendar 2019 7日目の記事です。
Ray.Di を実務に使い始めてそろそろ1年半になります。
今回はもともとSymfony2でyamlにDI設定を書くのに慣れていた私が、Ray.Diを使うときに使っている技の一部をご紹介したいと思います。
小技集
パラメータ(stringやint)の注入
Symfonyでは %
で囲むことでサービスにパラメータを注入できます。
# services.yml
parameters:
app_my_param: 123
services:
App\Service\SleighService:
class: App\Service\SleighService
arguments:
- '%app_my_param%'
bind()->annotatedWith('app_my_param')->toInstance(123)
のように、 annotatedWith()
を使って名前を指定することで、文字列や数値も注入できます。
annotatedWith
で指定した名前は注入したい先のクラスに toConstructor()
したり Provider
に @Named
で注入したりして使います。
<?php
class MyModule extends AbstractModule
{
protected function configure()
{
$this->bind()->annotatedWith('app_my_param')->toInstance(123);
$this->bind(SleighService::class)->toConstructor(SleighService::class, [
'myArg' => 'app_my_param',
]);
}
}
factory
とあるクラスをFactoryクラスを使って生成しなければならないとき、Symfonyならこう書きますね。
# services.yaml
parameters:
app_my_param: 123
services:
App\Service\SleighServiceFactory:
class: SleighServiceFactory
App\Service\SleighService:
class: App\Service\SleighService
factory: ['@App\Service\SleighServiceFactory', 'create']
arguments:
- '%app_my_param%'
RayにはFactoryを指定するメソッドはありません。
注入先クラスにFactoryそのものを注入するか、
<?php
class ReindeerService
{
public function __construct(SleighServiceFactory $sleighServiceFactory)
{
$this->sleighServiceFactory = $sleighServiceFactory;
}
public function execute()
{
// ...
$sleighService = $sleighServiceFactory->create($myArg);
// ...
}
}
Provider
を定義してその中でFactoryを使うようにします。
<?php
class MyModule extends AbstractModule
{
protected function configure()
{
$this->bind()->annotatedWith('app_my_param')->toInstance(123);
$this->bind(SleighService::class)->toProvider(SleighServiceProvider::class);
}
}
class SleighServiceProvider implements ProviderInterface
{
private $myArg;
/**
* @Named("myArg=app_my_param")
*/
public function __construct(int $myArg)
{
$this->myArg = $myArg;
}
public function get()
{
return new SleighService($this->myArg);
}
}
calls
インスタンスを作った後になにかのメソッドコールが必要な場合、Symfonyでは下記のように書くことができます。
# services.yml
services:
App\Service\SleighService:
class: App\Service\SleighService
calls:
- ['setReindeer', ['@App\Service\Reindeer']]
Rayにはcallsはありません。
Factoryクラスを用意して、Factoryクラスを注入します。(1個前の例参照)
あるいは、DIの領域内で解決したいときはProviderを作ってその中でcallしたものを渡すといいかもしれません。
<?php
class SleighProvider implements ProviderInterface
{
private $reindeer;
/**
* @Named("reindeer=app_service_reindeer")
*/
public function __construct(Reindeer $reindeer)
{
$this->reindeer = $reindeer;
}
public function get()
{
$sleighService = new SleighService();
$sleighService->setReindeer($this->reindeer);
return $sleighService;
}
}
同じ具象クラスのコンストラクタ引数だけ変えた複数のオブジェクトを使いたい
# services.yml
services:
brown_nose_reindeer:
class: App\Reindeer
arguments:
- "brown"
red_nose_reindeer:
class: App\Reindeer
arguments:
- "red"
App\Service\SleighService:
class: App\Service\SleighService
calls:
- ['addReindeer', ['@brown_nose_reindeer']]
- ['addReindeer', ['@red_nose_reindeer']]
Rayでも annotatedWith()
を使ってそれぞれに別名を付けて扱うことができます。
<?php
class MyModule extends AbstractModule
{
protected function configure()
{
$this->bind(Reindeer::class)->annotatedWith('brown_nose_reindeer')->toConstructor(Reindeer::class, [
'nose_color' => 'nose_color_brown',
]);
$this->bind(Reindeer::class)->annotatedWith('red_nose_reindeer')->toConstructor(Reindeer::class, [
'nose_color' => 'nose_color_red',
]);
}
}
あるいは、 abstract classの邪悪じゃない使い方 に変えることもできます。
<?php
class RedNoseReindeer extends AbstractReindeer
{
public function __construct()
{
parent::__construct('red');
}
}
class RedNoseReindeer extends AbstractReindeer
{
public function __construct()
{
parent::__construct('red');
}
}
class MyModule extends AbstractModule
{
protected function configure()
{
$this->bind(BrownNoseReindeer::class)->annotatedWith('brown_nose_reindeer')->to(BrownNoseReindeer::class);
$this->bind(RedNoseReindeer::class)->annotatedWith('red_nose_reindeer')->to(RedNoseReindeer::class);
}
}
パッケージを分けて開発していて、DI設定をマージしたい
SymfonyではyamlやxmlでDI設定を書いておいて、アプリケーションから読み込む仕組みがありますね。
Rayでは各パッケージごとに定義したModuleを、メインのアプリケーションのModuleをinstallすることで、DIを再利用することができます。
<?php
class ReindeerModule extends AbstractModule
{
protected function configure()
{
$this->bind(BrownNoseReindeer::class)->annotatedWith('brown_nose_reindeer')->to(BrownNoseReindeer::class);
$this->bind(RedNoseReindeer::class)->annotatedWith('red_nose_reindeer')->to(RedNoseReindeer::class);
}
}
class AppModule extends AbstractModule
{
protected function configure()
{
$this->install(new ReindeerModule());
$this->bind(SleighService::class)->toProvider(SleighProvider::class);
// SleighProviderの中でbrown_nose_reindeerやred_nose_reindeerを参照できる
}
}
まとめ
SymfonyのDIは強力ですが、コマンドラインで動かすちょっとしたツール的なライブラリで使うにはちょっとオーバースペックかなと思っています。
そういうときにはサクッと便利に使えるRay.Diがおすすめです。