Laravelのサービスプロバイダ
前回、Laravelをデプロイしみて動くようにはなったのですが、サービスプロバイダーについてまだよくわかっていないので、それについて整理しようと思います。
サービスプロバイダー
サービスプロバイダは、Laravelのアプリケーションの初期化を行います。サービスコンテナバインディングをはじめ、イベントリスナーやミドルウェア、ルートの登録などをします。
文字通りサービスを提供するもので、アプリとしての機能の実体はサービスコンテナによって定義されます。また、デフォルトで起動されるサービスプロバイダーはapp.php内のprovidersのキーにセットされています。
すべのサービスプロバイダはIlluminate¥Support¥ServiceProviderを継承します。そのメソッドにはregisterとbootがあります。registerは前述のサービスコンテナバインディングを記述する箇所です。言い換えると、サービスのインスタンスを生成するところです。
registerではサービスコンテナバインディングのみを記述し他の関連処理はbootメソッドに記述します。これは、一旦すべてのサービスプロバイダのregisterが呼ばれそのあとにbootを呼ばれるようになっている為です。registerメソッド内では、他のサービスのインスタンスが生成されておらずそれらを利用できない事があります。
ちなみに、コンテナのコンストラクタや初期化の作業が不要なら、サービスプロバイダーを使ってコンテナのインスタンスを作成しなくても、Laravelではタイプヒントするだけで自動的にインスタンスを生成してくれます。
あまり行儀のいいサンプルではありませんが、ルーターであるweb.phpにクラスを書いてインスタンスを生成せずに引数として設定します。
web.php
class TestService {
public $ids=array('0001','0002');
}
Route::get('/test', function (TestService $t) {
return $t->ids;
});
/*
* 結果は次のようになります
* ["0001","0002"];
*
*/
このような中で、サービスプロバイダを使う利点は、次のようなことがあげられると思います。まず、該当のクラスが他のクラスをコンストラクタに持ち依存している際に、そのインスタンスも自動生成してくれるためコードの省力化ができる。appヘルパー関数からインスタンスを取得できるようになり、それを利用してコードを書けば拡張クラスへのサービスコンテナの差し替え等が容易になる。シングルトンの管理が楽(これについては後述します)。といったことがあげられます。
サービスプロバイダの作成
実際にサービスプロバイダを作成してみます。
さきほどWeb.phpに書いたTestServiceクラスを独立させ、TestServiceをApp¥Services¥TestService.phpに持っていきます。その際、名前空間を指定し「namespace App¥Services;」とします。このファイルがサービスコンテナとなります。
web.php
<?php
namespace App¥Services;
class TestService {
public $ids=array('0001','0002');
}
次にコンテナのサービスプロバイダーを作成します。
PS > php artisan make:provider TestServiceProvider
ServiceProviderクラスを継承したファイルがApp¥Providersに作成されますので、registerメソッドでサービスコンテナのインスタンスを生成する記述を加えます。
TestServiceProvider.php
...
use App¥Services¥TestService;
...
public function register(){
$this->app->bind(TestService::class,function($app){
$wk = new TestService();
$wk->ids[]='0005';
return $wk;
});
}
...
このbindメソッドはTestServiceクラスとそのインスタンスの結びつけを行っています。関数の戻り値として返すインスタンスは、通常は、TestServiceクラスに対して、TestServiceのインスタンスを返すように設定しますが、サンプルコードのようにインスタンスに修正を加えたり、子孫クラスを返したりすることもできます。
サービスプロバイダの作成はこれで終わりです。もし他のファサード、たとえばDBファサード等を使ってさらにTestServiceのインスタンスに変更を加えたいようなことがあれば、bootメソッドに記述をします。特にすることがなければbootメソッドは空のままにしておきます。
プロバイダは作成しただけでは自動起動しませんので、アプリと共に起動させるようにします。その設定はconfig¥app.phpにあります。
app.php
...
'providers' => [
...
// 既存のプロバイダー
App¥Providers¥RouteServiceProvider::class,
// 配列の最後に追加します。
App¥Providers¥TestServiceProvider::class,
],
...
こうして実行すると、結果が["0001","0002","0005"];と変わります。
今回はプロバイダーまで作成しましたが、通常は、既に自動読み込みの対象となっているAppServiceProviderでコンテナをバインドすればいいと思います。
バインドするとappヘルパ関数から、app(TestService::class)とするか、app()->make(TestService::class)とすることで、インスタンスを取得できます。
シングルトン
シングルトンとは、ひとつのクラスがあった際そのクラスのインスタンスがアプリ全体を通してひとつだけである状態をさします。
サービスプロバイダでregisterでアプリをバインドする際に、bindではなくsingletonメソッドを使うことでそれが保証されるようになります。
singletonをどのような時に利用するかの例ですが、例えば管理者ユーザーをリストしておく次のようなサービスコンテナがあったとします。
ManagerService.php
class ManagerService {
public $managers=array();
public function initManagers() {
$this->managers=array();
$this->managers[]="安藤";
$this->managers[]="池田";
...
オブジェクトを次のように操作します。
...
$clsManager = app(ManagerService::class);
$cls->initManager();
// ※1
var_dump($clsManager->managers);
$clsManager = app(ManagerService::class);
// ※2
var_dump($clsManager->managers);
bindを利用しした場合、クラスのインスタンスは都度生成されます。そのため※1では["安藤","池田"]、※2では[](空の配列)が表示されます。
singletonを利用するとインスタンスは常にひとつにになります。存在しない場合は作成されて、存在する場合は既存のインスタンスが返ります。そのため※1、※2とも["安藤","池田"]が表示されます。
ただし、これはセッション変数のような永続的なものではありません。レスポンスを返したあとはインスタンスは破棄されます。