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

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

# 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')の結果は同じになります。