今回の記事では、PHPのフレームワークであるLaravelのDIに関する機能と実装方法を紹介します。Laravelの基礎知識に関してはこちらをご覧ください。
大雑把に説明すると、DI(Dependency Injection)とはあるコンポーネントで利用する別のコンポーネントを外部から注入できるようにするデザインパターンです。コンポーネント間を疎結合にするために、コンポーネント間の関係には具体的なコンポーネントではなくインタフェースを用います。DIに関してはこちらが参考になります。
サービスコンテナはLaravelアプリケーションにおいてインスタンスの管理を行う機能です。サービスコンテナの役割は大きく分けて二つあります。一つはインスタンスの生成方法の管理、もう一つは生成したインスタンスの管理です。
サービスプロバイダはインスタンスの生成方法をサービスコンテナに登録する役割を担います。
ここでは、サービスコンテナにインスタンスの生成方法を登録する方法(バインド)とサービスコンテナを利用したインスタンスの生成方法(解決)について説明します。
バインドには下記の四つの方法が用意されています。
まず始めに一般的なbindメソッドの使用方法を説明します。下記のサンプルコードの通り、 app()->bind() メソッドの第一引数に文字列(登録名)、第二引数にクロージャ(生成方法)を指定します。
<? 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メソッドは引数に指定した文字列に対応したバインドが存在しない場合にバインドを行います。
<? 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メソッドでバインドした場合、生成したインスタンスはサービスコンテナにキャッシュされます。
<? 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メソッドは生成したインスタンスをサービスコンテナにバインドします。
<? 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() ヘルパ関数で行います。
<? 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 に定義を追加します。
'providers' => [ // 省略 App\Providers\MyServiceProvider::class, // 追加 ],
サービスプロバイダクラスにはregisterメソッドとbootメソッドがあります。バインド処理は基本的にregisterメソッドに記述します。ただし、インスタンス生成時に他のクラスを利用する必要がある場合はbootメソッドに記述します。
ここではLaravelでのDIの利用方法を紹介します。まず、下記の用にインタフェースとクラスを定義し、バインドします。
<? 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);
上記のインタフェースをコンスタラクタインジェクションで利用する場合は下記のようになります。
<?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!'
メソッドインジェクションで利用する場合は下記のようになります。
<?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の利用にあたり重要な機能であるサービスコンテナとサービスプロバイダについて説明しました。