目的
Laravelには強力な機能の1つに、依存性注入(Dependency Injection)というものがあるようです。
サービスコンテナやサービスプロバイダといったといった用語も出てくるので
今回はこの機能について調べてみました。
依存性注入とは何か?
コントローラーのアクションメソッドを見ると、
public function index(Request $request) {
という記述を見かけます。
引数にクラス名とそのインスタンス変数が書かれており、
インスタンスが渡されてきているように見えます。
誰がこの引数にRequestクラスのインスタンスを渡してくれるのだろうか?
という疑問が出てきます。
調べてみると、Laravelのサービスコンテナという機能によって、
Laravel自身によって必要なインスタンスを生成し渡してくれるようです。
つまり、下記のように自分で new 演算子を使ってインスタンスを作らなくてもよさそうです。
$request = new Request()
自分でインスタンス生成する記述は、そのクラスに対して依存性を持つことになり、
クラスを記述する側とされる側との結び付きた強くなります。
プログラム設計をする場合、できる限りモジュール同士は疎結合になっていることが望ましく、それと同様、クラス同士も疎になっているべきです。 従って、あるクラスの中で、別のクラスのインスタンスを生成するという記述はあまり望ましくないと思われます。
自分でインスタンス生成することなく、外部で作成され(管理されている)インスタンスを引数で渡す(注入する)方式だと、自分のクラスとその他のクラスとの間が疎結合になり、不具合をおこしにくく保守性が高められるということになります。
これを依存性注入(DI)というらしく、コンストラクタに渡される場合はコンストラクトインジェクションメソッドに渡される場合は、メソッドインジェクションとも言われるようです。
引数を利用した依存性注入は、引き数を書くだけで自動的にインスタンスが作成されますが、サービスコンテナに依頼して自分で明示的にインスタンスを生成することもできます。
明示的インスタンス生成
$obj = app()->make('App\CalcClass');
$obj = app('App\CalcClass');
app()は、サービスコンテナのインスタンスを返します。そのインスタンスには
makeやresolveなどのメソッドがあり、それらを使うことでサービスコンテナに
インスタンスを生成してもらうことができます。
依存性注入を試してみる
app/Services/SpeekService.php
<?php
namespace App\Services;
class SpeekService
{
private $msg;
public function __construct()
{
$his->msg = 'Hello';
}
public function speek()
{
return $this->msg;
}
}
上記のサービスを、サービスコンテナによって作ってもらうことにします。
そうすることで、依存性注入を実現できるようになりますので試してみます。
app/Http/Controllers/TestController.php
<?php
namespace App\Http\Controllers;
use App\Services\SpeekService;
class TestController extends Controller
{
public function index(SpeekService $speekservice)
{
$data = ['msg' => $speekservice->speek()];
retur view('test.index', $data);
}
}
config/web.php
Route::get('/test', 'TestController@index')->name('test');
resources/views/test/index.blade.php
<body>
<p>{{$msg}}</p>
</body>
/testにアクセスすると、ブラウザには hello という文字が表示されます。
サービスコンテナという機能により、引き数のSpeekServiceインスタンスは
自動で用意され引数に渡してくれることが確認できます。
メソッドに渡されるケースなのでメソッドインジェクションと言われるます。
このように、自動で生成するクラスを集めて箱(コンテナ)の中に入れておき、
必要に応じてそこから引き出せる仕組みのことです。
しかも、自分が作りたいサービスのインスタンス以外にも、
システムが既に用意済みのインスタンスが入っていれば、それらも当然利用できます。
これがサービスコンテナの機能です。
サービスプロバイダ
自分で明示的にサービスコンテナに組み込むには、サービスコンテナに用意
されている app()->bind() というメソッドを使います。
サービスコンテナへの組み込み処理のことを結合といいます。
Laravelでは、サービスプロバイダという機能をつかって
サービスコンテナに組み込むという仕組みを持っています。
サービスプロバイダを使わずに、自分で好き勝手にサービスコンテナに登録することも
可能ですが、システム全体の中でそれを専門にやってくれる機能が用意されており、
それがサービスプロバイダと言われるようです。
app/Providers/AppServiceProvider.php
public function boot()
{
app()->bind('App\Services\SppekService', function($app){
$speekservice = new SpeekService();
return $speekservice;
});
}
第二引き数のクロージャ―では、new SpeekServiceでインスタンスを作成し、
それをリターンしているだけです。
これは、サービスコンテナが自動的に組み込んだインスタンスではなく、
bindによって明示的に組み込んだインスタンスを利用するやり方です。
今回はAppServiceProviderを使ってみましたが、自分でサービスプロバイダを作る
こともできるようです。
php artisan make:provider MyServiceProvider
このコマンドによって MyServiceProvider.php が自動生成されるので、
その中に記述されている register() メソッドに追加します。
class MyServiceProvider extends ServiceProvider
{
public function register()
{
/* ここにbindの処理を記述 */
}
public function boot()
{
}
}
register()は登録処理を行う記述をして、boot()には必要に応じて登録したサービスの
初期化処理を書きます。
サービスプロバイダを使って登録しているサービスは他にもあるので、それらの登録処理が一通り終わったあとに、boot()が順次実行されていくようです。
なので、あるサービスを登録する処理に、別のサービスがまだ登録を済ませてないために
エラーとなってしまう、というのを防ぐことができます。
調べたりして学んだこと
- Laravelではサービスコンテナという強力な機能を持っていることを知りました
- サービスプロバイダやサービスコンテナの仕組みが少し理解できました
- 次はシングルトンで結合する仕組みやインターフェースを利用した疎結合のやり方についても書こうと思います