何かと話題のPHPでのDIについてまとめてみました。
そもそも DI(Dependency Injection)ってなんぞ?
その名の通り、 __依存性(Dependency)__の __注入(Injection)__です。
依存をクラス内で生成せずに外から設定します。
まだパッとしないので具体例を挙げて説明してみます。
まずDIでないパターン
class Car
{
/**
* @var EngineInterface
*/
private $engine;
public function __construct()
{
$this->engine = new Engine();
}
public function run()
{
$energy = $this->engine->burn();
}
}
このコードの良くない点
- 別のエンジンに変えたい時に
Car.php
を修正しなければならない。 - エンジンがうまく動作しない時のテストはほぼ不可能
-
Engine
が完成するまでCar
を作る事ができない。
DIパターンを適用してみる
方法1: コンストラクターインジェクション
class Car
{
/**
* @var EngineInterface
*/
private $engine;
/**
* @param EngineInterface $engine
*/
public function __construct(EngineInterface $engine)
{
$this->engine = $engine;
}
public function run()
{
$energy = $this->engine->burn();
}
}
方法2: セッターインジェクション
class Car
{
/**
* @var EngineInterface
*/
private $engine;
/**
* @param EngineInterface $engine
*/
public function setEngine(EngineInterface $engine)
{
$this->engine = $engine;
}
public function run()
{
$energy = $this->engine->burn();
}
}
これで先述の問題点は解決できました。
1.別のエンジンに変えたい時にCar.php
を修正しなければならない。
Car
を使う側が好きにすれば良い。
2.エンジンがうまく動作しない時のテストはほぼ不可能
テスト時に設定するエンジンを変えてしまえば良い。
3.Engine
が完成するまでCar
を作る事ができない。
開発中は仮のエンジン(モック)を作って使えば良い。
DIとはこういう事
意外と簡単ですねー
だが依存を手動で解決するには少し大変という話
class CarFactory
{
public function get($type)
{
if ($type === 'economy') {
return new Car(new HybridEngine(
new GasolineEngine(),
new ElectricMotor(new Battery())));
}
if ($type === 'monster') {
return new Car(new V8Engine(6600));
}
}
}
引数が多くて管理しきれなくなる事は見えてます。
そこで登場するのが DIコンテナ
DI コンテナ
依存関係を定義しておくと、オブジェクトの生成時に依存を解決してくれるという代物です。
どう定義するかは、実際にコードを見た方が分かりやすいのでココでは省略。
ちなみにPHP製DIコンテナは意外と沢山あります。
有名どこはこんな所でしょうかね
-
Symfony/DependencyInjection
http://symfony.com/doc/current/components/dependency_injection/introduction.html -
Zend\Di
http://framework.zend.com/manual/2.0/en/modules/zend.di.introduction.html
Symfony/DependencyInjectionでサンプル
バンドルを作った際に{バンドルディレクトリ}/DependencyInjection/{バンドル名}BundleExtention.php
というファイルが生成されていると思います。
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// デフォルトはXMLなのでYAMLフォーマットに変更します。
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
Resources/config/services.yml
をDIの定義ファイルとして指定したので、先述のCarFactory.php
を定義として記述してみます。
services:
car.hybrid:
class: Hoge\Car
arguments: [@engine.hybrid]
engine.hybrid:
class: Hoge\Engine\Hybrid
arguments: [@engine.gasoline, @engine.motor]
engine.gasoline:
class: Hoge\Engine\Gasoline
engine.motor:
class: Hoge\Engine\Motor
arguments: [@battery]
battery:
class: Hoge\Battery
- それぞれを サービス として定義
- 依存しているサービスをメソッドの引数として定義
これで$container->get('car.hybrid')
と$factory->get('economy')
の結果は同じになります。