Laravelで1つのインターフェイスで別々のクラスをインジェクションする

投稿者: | 2019/05/07

目標

表題通りだが1つのインターフェイスからコントローラーに対して別々のクラスをインジェクションしたい!

Controller1 → CommonInterface → Repository1をインジェクション
Controller2 → CommonInterface → Repository2をインジェクション

できればテストでは別のリポジトリに切り替えたい

おさらい

Laravelには依存性の注入(Dependency Injection)という機能があります。
これをやりたい動機としてはオブジェクトを生成したところ(newしたところ)はそのオブジェクトに依存してしまうため、これをどっか別の設定ファイルとかに書いて依存部分を明らかにしてテストなどでは切り替えられたら幸せだなぁ、というところだと解釈しています。

一方でLaravelのよくあるDIの説明ではコンストラクタインジェクションがあります。
以下のような感じです。

class UserController extends Controller
{
    protected $users;

    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

これはnew UserRepository()みたいなことはしないで引数で渡してやれば勝手に生成してくれる有難い機能です。(フォームバリデーションはこれの拡張かな?)
しかし一方でUserControllerクラスはUserRepositoryに依存しちゃっています。
この依存を避けるためUserInterfaceクラスを作り依存を解消します。

// インターフェイスを作り
class UserInterface{
...
}
class UserRepository implements UserInterface{
...
}
// インジェクションはインターフェイスで行う
class UserController extends Controller
{
    protected $users;

    public function __construct(UserInterface $users)
    {
        $this->users = $users;
    }
}

// プロバイダーで依存関係を設定する
$this->app->bind('UserInterface', 'UserRepository');

基本はこれで良いのですが、コントローラーごとにインターフェイスを作って実装を作る必要があるのが面倒です。インターフェイスは大体同じなので共通で使い、コントローラーとリポジトリの依存関係はどこかで設定したいものです。

問題解決

結論。プロバイダーの設定の仕方で変えられる。

    public function register()
    {
        $this->app->when(UserController1::class)
        ->needs(UserInterface::class)
        ->give(function () {
            return new UserRepository1();
        });

        $this->app->when(UserController2::class)
        ->needs(UserInterface::class)
        ->give(function () {
            return new UserRepository2();
        });
    }

ただし、1つ1つインジェクションする頻度は下がるので遅延プロバイダの設定をした方が良いと思われるので、以下の設定をプロバイダに追加します。

    protected $defer = true;

    public function provides()
    {
        return [UserInterface::class];
    }

コメントを残す

メールアドレスが公開されることはありません。