PHP入門:依存性の注入とコンテナについて

スポンサーリンク
PHP入門:依存性の注入とコンテナについて 用語解説
PHP入門:依存性の注入とコンテナについて
この記事は約7分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、PHP-DIにおける依存性の注入とコンテナについてご紹介します。

スポンサーリンク

背景

PHP-DIに触れる機会がありましたので、PHP-DIにおける依存性の注入とコンテナについて備忘として残しました。

詳細は下記の公式サイトをご覧ください。

依存性の注入とコンテナ

依存性注入(Dependency Injection)と依存性注入コンテナ(Dependency Injection Containers)は異なるものです。

  • 依存性注入は、より良いコードを書くための方法です。
  • コンテナは、依存関係の注入を支援するツールです。

依存性注入を行うためにはコンテナが必要ではありません。ただし、コンテナは助けになることがあります。

PHP-DIは、依存性の注入をより実用的にするためのライブラリです。

DIを使わないケース

DIを使用しないコードの大まかな動作は次のとおりです。

  1. アプリケーションがFoo(例:コントローラー)を必要とするので、アプリケーションがFooを作成する
  2. アプリケーションがFooを呼び出す
  3. FooがBar(例:サービス)を必要とするので、FooがBarを作成する。
  4. FooがBarを呼び出す。
  5. BarがBim(サービス、リポジトリなど)を必要とするので、BarがBimを作成する。
  6. Barが何かを行う。

DIを使うケース

DIを使用するコードの大まかな動作は次のとおりです。

  1. アプリケーションがFooを必要とし、FooがBarを必要とし、BarがBimを必要とするので、アプリケーションがBimを作成する。
  2. アプリケーションがBarを作成し、それにBimを渡す。
  3. アプリケーションがFooを作成し、それにBarを渡す。
  4. アプリケーションがFooを呼び出す。
  5. FooがBarを呼び出す。
  6. Barが何かを行う。

これは制御の逆転パターン(Inversion of Control)です。依存関係の制御が呼び出される側から呼び出す側に逆転します。

主な利点:呼び出しチェーンの一番上にいるのは常にあなたです。すべての依存関係を制御し、アプリケーションの動作方法を完全に制御できます。別の依存関係で置き換えることもできます(たとえば、あなたが作成したものと)。

例えば、ライブラリXがロガーYを使用していて、ロガーZを使用するようにしたい場合、依存性注入を使用すると、ライブラリXのコードを変更する必要はありません。

DIコンテナを使うケース

では、PHP-DIを使用したコードはどのように動作するのでしょうか。

  1. アプリケーションがFooを必要とし、アプリケーションがコンテナからFooを取得するので、コンテナがBimを作成する。
  2. コンテナがBarを作成し、それにBimを渡す。
  3. コンテナがFooを作成し、それにBarを渡す。
  4. アプリケーションがFooを呼び出す。
  5. FooがBarを呼び出す。
  6. Barが何かを行う。

要するに、コンテナは依存関係の作成と注入のすべての作業を引き受けます。

具体例

これは、クラシックな実装(newやシングルトンの使用)と依存性注入の比較の実例です。

DIを使用しないケース

以下のクラスがあるとしましょう。

class GoogleMaps
{
    public function getCoordinatesFromAddress($address) {
        // calls Google Maps webservice
    }
}
class OpenStreetMap
{
    public function getCoordinatesFromAddress($address) {
        // calls OpenStreetMap webservice
    }
}

従来の方法は次のとおりです。

class StoreService
{
    public function getStoreCoordinates($store) {
        $geolocationService = new GoogleMaps();
        // or $geolocationService = GoogleMaps::getInstance() if you use singletons

        return $geolocationService->getCoordinatesFromAddress($store->getAddress());
    }
}

このとき、GoogleMapsの代わりにOpenStreetMapを使用したい場合、どのようにすればよいでしょうか? StoreServiceのコードを変更し、GoogleMapsを使用するすべての他のクラスも変更する必要があります。

DIを使用するケース

StoreServiceがDIを使用しています。

class StoreService {
    private $geolocationService;

    public function __construct(GeolocationService $geolocationService) {
        $this->geolocationService = $geolocationService;
    }

    public function getStoreCoordinates($store) {
        return $this->geolocationService->getCoordinatesFromAddress($store->getAddress());
    }
}

そして、サービスはインターフェースを使用して定義されています。

interface GeolocationService {
    public function getCoordinatesFromAddress($address);
}

class GoogleMaps implements GeolocationService { ...

class OpenStreetMap implements GeolocationService { ...

これで、StoreServiceのユーザーがどの実装を使用するかを決定することになります。いつでも変更でき、StoreServiceを書き直す必要はありません。

StoreServiceは依存性に強く結合されていなくなりました。

PHP-DIを使用するケース

依存性注入には1つの欠点があることに気づいたかもしれません。下記のコードのように依存関係の注入を扱う必要があるということです。

ここで、コンテナ、特にPHP-DIが助けてくれます。

$geolocationService = new GoogleMaps();
$storeService = new StoreService($geolocationService);

上記のように書く代わりに、

$storeService = $container->get('StoreService');

のように書けます。

$container->set('StoreService', \DI\create('GoogleMaps'));

そして、どのGeolocationServiceをPHP-DIが自動的にStoreServiceに注入するかを上記のように構成できます。

なので、使用する実装を変えることになった場合、変更するのは上記の構成の1行だけになります。

おわりに

今日は、PHP-DIにおける依存性の注入とコンテナについてご紹介しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました