PHPでDI（Dependency Injection）


何かと話題のPHPでのDIについてまとめてみました。

## そもそも DI（Dependency Injection）ってなんぞ？
その名の通り、 __依存性（Dependency）__の __注入（Injection）__です。

依存をクラス内で生成せずに外から設定します。
まだパッとしないので具体例を挙げて説明してみます。

### まずDIでないパターン

```php:Car.php
class Car
{
    /**
     * @var EngineInterface
     */
    private $engine;

    public function __construct()
    {
        $this->engine = new Engine();
    }

    public function run()
    {
        $energy = $this->engine->burn();
    }
}
```

#### このコードの良くない点

1. 別のエンジンに変えたい時に`Car.php`を修正しなければならない。
2. エンジンがうまく動作しない時のテストはほぼ不可能
3. `Engine`が完成するまで`Car`を作る事ができない。

### DIパターンを適用してみる
#### 方法1: コンストラクターインジェクション
```php:Car.php
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: セッターインジェクション
```php:Car.php
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とはこういう事
意外と簡単ですねー

### だが依存を手動で解決するには少し大変という話

```php:CarFactory.php
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コンテナは意外と沢山あります。
有名どこはこんな所でしょうかね

* Pimple
  http://pimple.sensiolabs.org/

* Symfony/DependencyInjection
  http://symfony.com/doc/current/components/dependency_injection/introduction.html

* Aura.Di
  https://github.com/auraphp/Aura.Di

* Ray.Di
  https://github.com/koriym/Ray.Di

* Zend\Di
  http://framework.zend.com/manual/2.0/en/modules/zend.di.introduction.html

### Symfony/DependencyInjectionでサンプル

バンドルを作った際に`{バンドルディレクトリ}/DependencyInjection/{バンドル名}BundleExtention.php`というファイルが生成されていると思います。

```php:HogeBundleExtention.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`を定義として記述してみます。

```yaml:Resources/config/services.yml
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

```

1. それぞれを __サービス__ として定義
2. 依存しているサービスをメソッドの引数として定義

これで`$container->get('car.hybrid')`と`$factory->get('economy')`の結果は同じになります。