LaravelのDI(DependencyInjection:依存性注入)を使ったサンプルプログラムです。
仕様概要
今回作るアプリケーションは占いの結果を表示してくれるものです。
ページを開くとおみくじを引き「大吉~大凶」までの占いしてくれます。
回答のパターン以下の6パターンです
- 大吉
- 中吉
- 小吉
- 末吉
- 吉
- 凶
- 大凶
この中からランダムで1つを選択して表示します。
通常占いクラスを作成た場合は
プログラム内で
$uranai = new Fortune();
などと記述してFortuneController内でFortuneクラスをインスタンス化していると思います。
しかしこの方法ではFortuneControllerはFortuneクラスに依存していることとなり
Fortuneクラスが変更された場合にはFortuneControllerも変更しなければなりません
そこでDIを利用してFortuneControllerの変更なしにコンポーネントを差し替える方法を紹介します
インターフェースの作成
まずはインターフェースを作成します。
このインターフェースを継承したものは同じように扱えるようにするため
インターフェースを作成することで機能を抽象化します。
おみくじのインターフェースを作成します、uranai関数を定義し
このインターフェースを実装した関数を後で作成します。
app/Interfaces/FortuneInterface.php
<?php namespace App\Interfaces; interface FortuneInterface { public function uranai(): string; }
コンポーネント(関数の実体)を作成します。
おみくじプログラムの本体です、ここにおみくじの仕組みを作成します。
app\Componentsフォルダを作成します。
app\Components\Fortune.php
<?php namespace App\Components; use App\Interfaces\FortuneInterface; const FORTUNE = [ 0 => '大吉', 1 => '中吉', 2 => '小吉', 3 => '末吉', 4 => '吉', 5 => '凶', 6 => '大凶', ]; class Fortune implements FortuneInterface { public function uranai(): String { // 0~6の乱数を生成 $fortune = rand(0, 6); return self::FORTUNE[$fortune]; } }
サービスを作成します
App\Services\FortuneService.php
<?php namespace App\Services; use App\Interfaces\FortuneInterface; class FortuneService { protected $FortuneComponent; public function __construct(FortuneInterface $FortuneComponent){ $this->FortuneComponent = $FortuneComponent; } public function uranai(){ return $this->FortuneComponent->uranai(); } }
コントローラを作成します。
ここがLaravelのコントローラとなり、入り口になります。
App\Http\Controllers\FortuneController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Services\FortuneService; class FortuneController extends Controller { protected $FortuneService; public function __construct(FortuneService $FortuneService) { $this->FortuneService = $FortuneService; } public function index(Request $request) { echo "<h1>".$this->FortuneService->uranau()."<h1>"; } }
サービスプロバイダを作成する。
DI(依存性注入)利用において、注入するインスタンスの生成、また、クラスへの注入も、サービスコンテナが担う。
サービスプロバイダでComponentを注入する。
php artisan make:provider FortuneServiceProvider
個のコマンドでサービスプロバイダが作成される
以下のように修正する
app\Provider\FortuneServiceProvider.php
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Services\Interfaces\FortuneInterface; // 追加 class FortuneServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app->bind(FortuneInterface::class, function() { return new \App\Components\Fortune(); }); } /** * Bootstrap services. * * @return void */ public function boot() { // } }
サービスプロバイダをプロバイダのオートロードに登録する
config/app.php
... 'providers' => [ ... ... App\Providers\FortuneServiceProvider::class, // 最後に追記 ]
ブラウザで動作させるためにRoutingに追記
... use App\Http\Controllers\FortuneController; ... ... ... Route::get('/', [FortuneController::class, 'index']); ...
動作確認
サイトを開くと占いの結果が表示されれば完成
url: localhost
大吉
テスト
ここまでコンポーネント作成し、インターフェースを定義し、サービスを作り、コントローラに読み込みと
面倒な作業をしてきましたが、ここからがDIを使うメリットが出てきます。
テストを作成する場合に結果が大吉や大凶など毎回違う値が返ってくると
テストがし辛いので
テスト用にapp\Components\TestFortune.phpコンポーネントを作成します。
このコンポーネントはFortuneInterfaceを継承しuranauメソッドを定義します、このとき必ず大吉が返却されるように細工をします。
app\Components\Fortune.php
<?php namespace App\Components; use App\Interfaces\FortuneInterface; const FORTUNE = [ 0 => '大吉', 1 => '中吉', 2 => '小吉', 3 => '末吉', 4 => '吉', 5 => '凶', 6 => '大凶', ]; class TestFortune implements FortuneInterface { public function uranai(): String { // 0~6の乱数を生成 // $fortune = rand(0, 6); // 0必ず0の大吉を返す $fortune = 0; return self::FORTUNE[$fortune]; } }
サービスプロバイダを編集
app\Provider\FortuneServiceProvider.php
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Services\Interfaces\FortuneInterface; // 追加 class FortuneServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app->bind(FortuneInterface::class, function($app) { if (app()->runningUnitTests()) { return new \App\Components\TestFortune(); }else{ return new \App\Components\Fortune(); } }); } /** * Bootstrap services. * * @return void */ public function boot() { // } }
これでユニットテストのときだけ「大吉」を返す処理ができました。