こんにちは。よっしーです(^^)
今日は、PHP-DIにおける依存性の注入とコンテナについてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIにおける依存性の注入とコンテナについて備忘として残しました。
詳細は下記の公式サイトをご覧ください。
依存性の注入とコンテナ
依存性注入(Dependency Injection)と依存性注入コンテナ(Dependency Injection Containers)は異なるものです。
- 依存性注入は、より良いコードを書くための方法です。
- コンテナは、依存関係の注入を支援するツールです。
依存性注入を行うためにはコンテナが必要ではありません。ただし、コンテナは助けになることがあります。
PHP-DIは、依存性の注入をより実用的にするためのライブラリです。
DIを使わないケース
DIを使用しないコードの大まかな動作は次のとおりです。
- アプリケーションがFoo(例:コントローラー)を必要とするので、アプリケーションがFooを作成する
- アプリケーションがFooを呼び出す
- FooがBar(例:サービス)を必要とするので、FooがBarを作成する。
- FooがBarを呼び出す。
- BarがBim(サービス、リポジトリなど)を必要とするので、BarがBimを作成する。
- Barが何かを行う。
DIを使うケース
DIを使用するコードの大まかな動作は次のとおりです。
- アプリケーションがFooを必要とし、FooがBarを必要とし、BarがBimを必要とするので、アプリケーションがBimを作成する。
- アプリケーションがBarを作成し、それにBimを渡す。
- アプリケーションがFooを作成し、それにBarを渡す。
- アプリケーションがFooを呼び出す。
- FooがBarを呼び出す。
- Barが何かを行う。
これは制御の逆転パターン(Inversion of Control)です。依存関係の制御が呼び出される側から呼び出す側に逆転します。
主な利点:呼び出しチェーンの一番上にいるのは常にあなたです。すべての依存関係を制御し、アプリケーションの動作方法を完全に制御できます。別の依存関係で置き換えることもできます(たとえば、あなたが作成したものと)。
例えば、ライブラリXがロガーYを使用していて、ロガーZを使用するようにしたい場合、依存性注入を使用すると、ライブラリXのコードを変更する必要はありません。
DIコンテナを使うケース
では、PHP-DIを使用したコードはどのように動作するのでしょうか。
- アプリケーションがFooを必要とし、アプリケーションがコンテナからFooを取得するので、コンテナがBimを作成する。
- コンテナがBarを作成し、それにBimを渡す。
- コンテナがFooを作成し、それにBarを渡す。
- アプリケーションがFooを呼び出す。
- FooがBarを呼び出す。
- 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における依存性の注入とコンテナについてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント