はじめに
今回の記事では、PHPのフレームワークであるLaravelのDIに関する機能と実装方法を紹介します。Laravelの基礎知識に関してはこちらをご覧ください。
DIに関する機能
DIとは
大雑把に説明すると、DI(Dependency Injection)とはあるコンポーネントで利用する別のコンポーネントを外部から注入できるようにするデザインパターンです。コンポーネント間を疎結合にするために、コンポーネント間の関係には具体的なコンポーネントではなくインタフェースを用います。DIに関してはこちらが参考になります。
サービスコンテナ
サービスコンテナはLaravelアプリケーションにおいてインスタンスの管理を行う機能です。サービスコンテナの役割は大きく分けて二つあります。一つはインスタンスの生成方法の管理、もう一つは生成したインスタンスの管理です。
サービスプロバイダ
サービスプロバイダはインスタンスの生成方法をサービスコンテナに登録する役割を担います。
サービスコンテナ
ここでは、サービスコンテナにインスタンスの生成方法を登録する方法(バインド)とサービスコンテナを利用したインスタンスの生成方法(解決)について説明します。
バインド
バインドには下記の四つの方法が用意されています。
- bindメソッド
- bindIfメソッド
- singletonメソッド
- instanceメソッド
bind
まず始めに一般的なbindメソッドの使用方法を説明します。下記のサンプルコードの通り、 app()->bind() メソッドの第一引数に文字列(登録名)、第二引数にクロージャ(生成方法)を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <? class Message { private $name; public function __construct($name='Taro') { $this->name = $name; } public function sayHello() { return 'Hello '.$this->name.'!'; } } // 引数なしの場合 app()->bind(Message::class, function () { return new Message(); }); // 引数ありの場合 app()->bind(Message::class, function ($app, array $parameters) { return new Message($parameters[0]); }); |
bindIf
bindIfメソッドは引数に指定した文字列に対応したバインドが存在しない場合にバインドを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <? class Message { private $name; public function __construct($name='Taro') { $this->name = $name; } public function sayHello() { return 'Hello '.$this->name.'!'; } } // Hanakoで登録 app()->bindIf(Message::class, function () { return new Message('Hanako'); }); // Taroで登録 app()->bindIf(Message::class, function () { return new Message('Taro'); }); $message = app()->make(Message::class)->sayHello(); // $message = 'Hello Hanako!' |
singletonメソッド
singletonメソッドでバインドした場合、生成したインスタンスはサービスコンテナにキャッシュされます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <? class Message { private $name; public function __construct($name='Taro') { $this->name = $name; } public function sayHello() { return 'Hello '.$this->name.'!'; } } app()->singleton(Message::class, function ($app, array $parameters) { return new Message($parameters[0]); }); $message1 = app()->make(Message::class, ['Hanako'])->sayHello(); $message2 = app()->make(Message::class, ['Taro'])->sayHello(); // $message1 = 'Hello Hanako!' // $message2 = 'Hello Hanako!' |
instanceメソッド
instanceメソッドは生成したインスタンスをサービスコンテナにバインドします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <? class Message { private $name; public function __construct($name='Taro') { $this->name = $name; } public function sayHello() { return 'Hello '.$this->name.'!'; } } $message = new Message(); app()->instance(Message::class, $message); $sayHello = app()->make(Message::class)->sayHello(); // $sayHello = 'Hello Hanako!' |
解決
サービスコンテナを介したインスタンスの生成には app()->make() メソッドか app() ヘルパ関数で行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <? class Message { private $name; public function __construct($name='Taro') { $this->name = $name; } public function sayHello() { return 'Hello '.$this->name.'!'; } } app()->bind(Message::class, function () { return new Message(); }); $message1 = app()->make('Message::class')->sayHello(); $message2 = app('Message::class')->sayHello(); // $message1 = 'Hello Taro!' // $message2 = 'Hello Taro!' |
サービスプロバイダ
サービスコンテナへのインスタンス生成方法のバインドは AppServiceProvider クラスや独自のサービスプロバイダクラスに定義します。独自のサービスプロバイダクラスを作成した場合は app.php に定義を追加します。
1 2 3 4 | 'providers' => [ // 省略 App\Providers\MyServiceProvider::class, // 追加 ], |
サービスプロバイダクラスにはregisterメソッドとbootメソッドがあります。バインド処理は基本的にregisterメソッドに記述します。ただし、インスタンス生成時に他のクラスを利用する必要がある場合はbootメソッドに記述します。
DIを利用する
ここではLaravelでのDIの利用方法を紹介します。まず、下記の用にインタフェースとクラスを定義し、バインドします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <? interface NoticeInterface { public function sayHello(string $name); } class Notice implements NoticeInterface { public function sayHello(string $name) { return 'Hello '.$name.'!' } } // インタフェースのバインド app()->bind(NoticeInterface::class, Notice::class); |
コンストラクタインジェクション
上記のインタフェースをコンスタラクタインジェクションで利用する場合は下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php class Message { private $notice; private $name; public function __construct(NoticeInterface $notice, $name='Taro') { $this->notice = $notice; $this->name = $name; } public function sayHello() { return $this->notice()->sayHello($this->name); } } app()->bind(Message::class, function () { return new Message(); }); $sayHello = app(Message::class)->sayHello(); // $sayHello = 'Hello Taro!' |
メソッドインジェクション
メソッドインジェクションで利用する場合は下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php class Message { private $notice; private $name; public function __construct($name='Taro') { $this->notice = $notice; $this->name = $name; } public function sayHello(NoticeInterface $notice) { return $this->notice()->sayHello($this->name); } } app()->bind(Message::class, function () { return new Message(); }); $message = app(Message::class); $sayHello = app()->call([$message, 'sayHello']); // $sayHello = 'Hello Taro!' |
さいごに
LaravelでのDIの利用にあたり重要な機能であるサービスコンテナとサービスプロバイダについて説明しました。