背景
最近開発しているプロジェクトでDIっぽい事をしているが、あまり理解していないのでDIの概要やDIコンテナについて説明していきます。
環境
- PHP 7.1
- Laravel 5.8
DIについて
wikiによるとDependency Injectionの略で依存性の注入と言う意味で、依存関係にあるオブジェクトを外から注入するデザインパターンの一種
依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、コンポーネント間の依存関係をプログラムのソースコードから排除するために、外部の設定ファイルなどでオブジェクトを注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。
引用元 : 依存性の注入 -Wikipedia
依存関係を解決する
DIにはコンストラクタインジェクション、セッターインジェクション、メソッドインジェクション等があるが今回はコンストラクタインジェクションを使って依存関係にあるクラスを解決する。
DIする前
下記のように、Dogクラス内でPomeranianクラスをインスタンス化している場合、PomeranianクラスはDogクラスに依存している事になる。
class Dog
{
private $dog;
public function __construct()
{
$this->dog = new Pomeranian();
}
// 吠える
public function bark(): string
{
return $this->dog->bark();
}
}
class Pomeranian
{
// 吠える
public function bark(): string
{
return 'Woof woof';
}
}
$dog = new Dog();
$bark = $dog->bark();
DIで外部からオブジェクトを注入する
依存関係を解決する為、まずインターフェイス(DogInterface.php)を作成する。
interface DogInterface
{
// bark()の定義を確約する
public function bark();
}
Dogクラス内部にあったPomeranianクラスと新しく追加するBuldogクラスにDogInterfaceをimplementsさせる。
class Pomeranian implements DogInterface
{
// 吠える
public function bark(): string
{
return 'Woof woof';
}
}
class Bulldog implements DogInterface
{
// 吠える
public function bark(): string
{
return 'Bow wow';
}
}
コンストラクタで外部からDogInterfaceを実装したオブジェクトを注入させる。
これによりbark()を定義しているクラスであれば外部から受け付けることができ、依存関係を解決する事ができる。
class Dog
{
private $dog;
public function __construct(DogInterface $instanse)
{
$this->dog = $instanse;
}
// 吠える
public function bark(): string
{
return $this->dog->bark();
}
}
$instanse1 = new Pomeranian();
$instanse2 = new Bulldog();
// DogInterfaceによって外部からのクラスを許容する
$pomeranian = new Dog($instanse1);
$buldog = new Dog($instanse2);
$pomeranian->bark(); // Woof woof
$buldog->bark(); // Bow wow
今回の場合はコンストラクタの引数にDogInterfaceしかないので問題はないが、下記のAnimalクラスのパターンを見てみると...
class Animal
{
private $dog;
private $cat;
private $bird;
public function __construct(
DogInterface $dog,
CatInterface $cat,
BirdInterface $bird)
{
$this->dog = $dog;
$this->cat = $cat;
$this->bird = $bird;
}
public function do()
{
// 何か処理する
}
}
// 複数インスタンス化しないといけないので
// ここを対策したい
$dog = new Dog();
$cat = new Cat();
$bird = new Bird();
$animal = new Animal($dog, $cat, $bird);
コンストラクタインジェクションは、Animalクラスのように複数のオブジェクトをコンストラクタに渡しているので、毎回インスタンス化しないといけないデメリットがある。
これを解決する為、DIコンテナを利用する。
DIコンテナ
DIコンテナとは、アプリケーションにDI機能を提供するフレームワークの事
DIコンテナを使って、先ほどDIで増えた複数のインスタンス生成をコンテナの変数でまとめる。
pimple
今回はDIコンテナのパッケージであるPimpleを使用する。
インストール
$ ./composer.phar require pimple/pimple ~3.0
"require": {
"php": "^7.1.3",
"fideloper/proxy": "^4.0",
"laravel/framework": "5.8.*",
"laravel/tinker": "^1.0",
"pimple/pimple": "^3.0"
},
container.phpを作成して、インスタンスの生成をまとめてコンテナに入れる。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Pimple\Container;
$container = new Container();
$container['Dog'] = function ($c) {
return new Dog();
};
$container['Cat'] = function ($c) {
return new Cat();
};
$container['Bird'] = function ($c) {
return new Bird();
};
// インスタンスを全てコンテナに入れる
$container['animal'] = function ($c) {
return new Animal($c['Dog'], $c['Cat'], $c['Bird']);
};
先ほどのAnimal.phpをコンテナを使って修正すると下記のようになり
class Animal
{
private $dog;
private $cat;
private $bird;
public function __construct(
DogInterface $dog,
CatInterface $cat,
BirdInterface $bird)
{
$this->dog = $dog;
$this->cat = $cat;
$this->bird = $bird;
}
public function do()
{
// 何か処理する
}
}
// 複数のインスタンス生成が
// $dog = new Dog();
// $cat = new Cat();
// $bird = new Bird();
// $animal = new Animal($dog, $cat, $bird);
// コンテナにより1行にできた
$animal = $container['animal']
コメント部分が削除でき、複数のインスタンスの生成をまとめる事ができた。
まとめ
DIやDIコンテナを使うことで、実装やテストが楽になりそうなので、有効的に使っていきたいと思います。
次回もLaravelの何かについて投稿できればと思っています。