Edited at

LaravelのDIでサービスコンテナを使って環境ごとにクラスを使いわける

開発環境の時はslackにさせたい(orさせたくない)とか、動作確認を簡単にするために艦初環境ではバリデーションをゆるくしたいなど、環境ごとに処理を分けたいことがあると思います。

直接コードの中に環境ごとの分岐を書くこともできるのですが、テスタブルではなくなるので、外からクラスを渡せるようにコンストラクターインジェクションを行い、環境に応じたクラスを使い分ける方法とテストコードについて書きます。


環境ごとに使いたいクラスを用意する

今回はシンプルにproductionの処理とそれ以外の環境での処理を分けるようにします。処理の内容は環境ごとに違う文字列を返すだけです。


production用クラス


Services/Message.php

<?php

namespace App\Services;

class Message
{
public function makeMessage()
{
return 'message';
}
}



その他用のクラス

先ほどのクラスを継承させて、開発環境などで使われるfakeのクラスを作成しました。


Services/FakeMessage.php

<?php

namespace App\Services;

class FakeMessage extends Message
{
public function makeMessage()
{
return 'fake message';
}
}


実際には通知先を変えるとか特定の処理を行わないとかもう少しこのクラス内でやることが変わるはずですがシンプルに。


呼び出し元

コンストラクタの引数にMessageクラスを渡せるようにしておきます。


Services/Notification.php

<?php

namespace App\Services;

class Notification
{
private $messageService;

// インスタンス生成時にMessageクラスを渡せるようにする
public function __construct(Message $messageService)
{
$this->messageService = $messageService;
}

public function send()
{
// どこかに通知をしたとする
return $this->messageService->makeMessage();
}
}



サービスコンテナに追加をする

環境ごとにクラスを使い分けたいので、サービスコンテナに記載します。

どのクラスを使うかは、configファイルから取得をします。

元のクラスがMessgeクラスでそれを継承しているクラスがFakeMessageなので、デフォルトではMessageクラスを使用して、使うクラスが指定されている場合のみそのクラスを使うようにします。


app/Providers/AppServiceProvider.php

public function register() {

if (!$messageClass = config('app.message')) {
// configで指定されていないので、Messageクラスを使う
$this->app->bind("App\\Services\\Message", "App\\Services\\Message");
} else {
// Messageクラスの代わりにconfigで指定されたクラスを使う
$this->app->bind("App\\Services\\Message", "App\\Services\\$messageClass");
}
}

こちらの部分で、configディレクトリapp.php のkeyがmessage の値を取得しています。

config('app.message')


新しくProvidersディレクトリ内に作成する場合

新しくファイルを作成する場合には、app.phpのprovidersに作成したクラスを追加します。


config/app.php

'providers' => [

App\Providers\NewProvider::class, // 追加
];


config

.envファイルから値を取得します。


config.app.php

'message' => env('MESSAGE_CLASS', ''),


下記envファイルからその環境で使うクラス名を指定します。(localやstagingなども同様に)


env.testing

MESSAGE_CLASS=FakeMessage


ここまでで環境ごとに使い分けられるようになりました。

各環境ごとにクラスを分ける場合にはenvファイルに追加すれば環境ごとに変えられます。


テストコード

そのまま使用をするとtestingの環境として実行されるため、本番環境で動くコードのテストが行えません。

そのためコンストラクタインジェクションで本番環境で実行されるクラスを渡すようにします。


test/Unit/NotificationTest.php


<?php

namespace Tests\Unit;

use App\Services\Message;
use App\Services\Notification;
use Tests\TestCase;

class NotificationTest extends TestCase
{
public function testNotification()
{
// FakeMessageではなくMessageクラスを使用する
$notification = new Notification(new Message());
$this->assertEquals('message', $notification->send());
}
}