目次
サービスクラスとDI注入
サービスクラスを作成します。
サービスプロバイダを介して、コントローラクラス内でサービスクラスをDI(依存性注入)して利用するやり方(Laravelのお作法)を確認します。
今回は、コントローラクラスの中で使うサービスクラスの作成をします。
Laravelに限らず、MVCモデルのコントローラクラスの中にロジックを全部書くなんてことは基本しません。
ロジック部分をサービスクラスに切り出して、それをコントローラクラスで使います。
そんなサービスクラスを作成して、DI(依存性注入)できるようにしていきます。
なお、DI(依存性注入)という言葉がわかりづらいので、DIについて書きます。
DI(依存性注入)
Laravelのドキュメントの説明では「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」ことだと書いてありますが、なんか難しそうに見えます。
でも。
実際のところ「クラスAを使用するクラスに対して、クラスAのオブジェクトを外部から暗黙的に渡す」ようにするだけなんですよね。
例えば。
Databaseとやりとりするアダプタの指定は、DIを使わないならこんな感じです。
$this->adapter = new MySqlAdapter;
でも。
これだと「MySqlAdapter;」クラスと、使っているクラスの結びつきは強いです。
DatabaseをMySqlAdapter以外でアクセスする必要があるときに、使っている箇所を探して、ゴリゴリ書き換えたりしないといけなくなります。
それでは保守性もおちるし、テストもしづらい。
そこで。
$this->adapter = $adapter;
みたいに書くようにして、この「$adapter」に対して、外部からどのクラスオブジェクト(例だとMySqlAdapter)を渡すかを変更できるようにすれば、疎結合になるので、テスト時にMock等で代替するのもしやすくなるし、長期的な保守性もあがります。
このやり方のことを「DI(依存性注入)」とよんでいます。
そして、この$adapterに対してクラスオブジェクトを外部から渡す方法に、コンストラクタを使う方法とメソッドの引数を使う方法があるということです。
DI(依存性注入)を実装するお作法の確認
Laravelで上記の「DI(依存性注入)」を実装する「お作法」を確認していきます。
Laravelではサービスコンテナという仕組を使います。
Laravelのドキュメントでは、サービスコンテナとは、「クラス間の依存を管理する強力な管理ツール」だとされています。
実際には。
サービスコンテナクラスみたいなソースを書いたりするわけではありません。
サービスプロバイダクラスを作成して、その中で「$this->app->bind()」メソッドを使って、サービスコンテナにクラスを登録します。
つまり、「$this->app」部分が、サービスコンテナを指しているというわけです。
仕組上。
このように「サービスコンテナ」に、DIしたいクラスを登録してすれば「DI(依存性注入)」できるようになるわけです。
お作法としては、こんな感じです。
サービスクラス実装手順
Laravel6.0の作法にそったサービスを作る手順は。
- app\Servicesフォルダに、サービスクラスを作成する。
- php artisan make:provider <privider name>コマンドでサービスプロバイダを生成する
- app/Providersフォルダに生成されたサービスプロバイダを編集する。
- config/app.phpのproviders=>に作成したサービスプロバイダを登録する。
です。
今回は手順確認だけが目的のダミーサービスなので名称も。
にします。
Viewとコントローラは、前回使ったものと同じにします。
ですね。
app\Servicesフォルダに、サービスクラスを作成する。
簡単にするために、前回「HomeController」内に直書きしていたロジック部分を外だしするだけのサービスクラスを作ります。
前回やっていたのは。
- Carbonを使って今日の日付を取得する。
- Carbonを使って10日後の日助を取得する。
- リスト1からリスト4の文字列の配列を作る
です。
ちなみに。
CarbonはPHPのDateTimeのBetter版といった感じの日付処理ライブラリです。
ちょっと検索すれば、サンプルはいくらでもでてくる位有名です。
さて。
このうち上の2つは「今日=0日後の日付」とも考えられるので、ひとつのメソッドにまとめることにしました。
結果、こんな感じになります。
app\Services\DummyService.php
<?php declare(strict_types=1); namespace App\Services; use Carbon\Carbon; class DummyService { public function getDate(int $later): string { return Carbon::now()->addDay($later)->format('Y年m月d日'); } public function getArray(): array { $tests = [ 1 => 'リスト1', 2 => 'リスト2', 3 => 'リスト3', 4 => 'リスト4']; return $tests; } }
php artisan make:providerコマンドでサービスプロバイダを生成
まずは、サービスプロバイダクラスを生成します。
プロジェクトフォルダをカレントにして。
を実行します。
成功すると以下のメッセージを表示します。
Provider created successfully.
よしよし。
app/Providersフォルダに生成されたサービスプロバイダを編集
上記で生成されたソースはこんな感じです。
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class DummyServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { // } }
registerとbootというメソッドが、上記ソースにはあります。
それぞれの役割は、下記リンクのLaravelのドキュメントに詳しく書かれていますが、簡単に要約してみるとこんな感じですかね
- register:サービスコンテナに何かを結合することだけを行なう。
- boot:view composerをサービスプロバイダで登録する。
今回作成するのは「サービスクラス」です。
なので「サービスコンテナ」を使います。
ということで・・。
registerメソッドが対象となります。
登録する部分を追加したサービスプロバイダクラスはこんな感じになります。
app\Providers\DummyServiceProvider.php
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Services\DummyService; class DummyServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app->bind('DummyService', DummyService::class); } /** * Bootstrap services. * * @return void */ public function boot() { // } }
DummyServiceをuseして、「$this->app->bind()」という、文字通りのことを「register」の中でやっているだけなのがよくわかります。
config/app.phpのproviders=>に作成したサービスプロバイダを登録
作成したDummyServiceProviderを、config\app.phpに登録します。
app.phpの中に「'providers' => []」という配列があって、そこにデフォルトで沢山のプロバイダがすでに記入されてます。
そこに、以下のような感じで追加します。
/*
* User Service Providers...
*/
App\Providers\DummyServiceProvider::class,
これで保存しておけば準備OKです。
コントローラクラスでサービスをDIして利用する
最後に、「HomeController.php」を修正して、DummyServiceクラスをDIして利用するようにします。
app\Http\Controllers\HomeController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Services\DummyService; class HomeController extends Controller { protected $dummy; public function __construct(DummyService $dummyService) { $this->middleware('auth'); $this->dummy = $dummyService; } public function index() { $today = $this->dummy->getDate(0); $thatday = $this->dummy->getDate(10); $tests = $this->dummy->getArray(); return view('home', compact( 'today', 'thatday', 'tests' )); } }
やっていることは。
- use App\Services\DummyService;する。
- protected $dummy;でクラスオブジェクトを注入する変数を定義する。
- コンストラクタに$this->dummyへ引数で渡したDummyServiceを代入する
- クラスメソッドを$this->dummy経由で呼び出して使う
ということだけです。
でも、こうしておくとですね。
例えばDummyServiceクラスの実装がまだFIXしていなくても、モックを使ってコントローラのテストもやれますし、仮に、サービスの中で利用しているクラス(今回だとCarbon)が何かの事情で使えなくなった時にも、変更はサービスの内部だけに局所化できて保守性もあがります。
実行イメージ
実行の仕方は前回と一緒で、MySQL動かして、「php artisan serve」でビルトインサーバーを動かして、localhostにアクセスするだけなので割愛します。
実行した結果がこちら。
正しく動いてます。
よしよし。
今回はこんなところで。
ではでは。