🏀

Laravelで "メソッドインジェクション" と "コンストラクタインジェクション" を体験してみた件

2022/06/05に公開

メソッドインジェクションやコンストラクタインジェクションについて詳しく書かれている記事は結構見かけるので、

この記事ではソースコードを見て端的にメソッドインジェクションとコンストラクタインジェクションの

概要をLaravelの内部動作関係なしに理解できるように、この記事を書きました。

interface_basketballplayer.php
<?php
namespace App\Test;

interface interface_basketballplayer
{
    public function shot();
}

このインターフェースを実装したものがlebronクラス

lebron.php
<?
namespace App\Test;

use App\Test\interface_basketballplayer;

class lebron implements interface_basketballplayer
{
    public function shot()
    {
        return "レブロンのダンクシュート";
    }
}

このクラスのインスタンスをコントローラーのアクションで使いたい時、以下のようにするだけで使うことができる。

TestController.phpの一部抜粋
        use App\Test\lebron;
    public function test(lebron $lebron)
    {
        echo $lebron->shot();
    }
    //ブラウザに"レブロンのダンクシュート"と表示される

なぜかインスタンス化しなくても、メソッドの引数にクラスとそのインスタンスが代入される変数を指定しているだけなのに、lebronクラスのインスタンスを使うことができています。
これがメソッドインジェクションです。
主にサービスコンテナが仕事をしてくれています。

次に考える例は引数に指定したクラスがコンストラクタを持っていた場合です。
例えば以下のようなクラスがあった場合

StarPlayer.php
<?php
namespace App\Test;

class StarPlayer
{
    private $player;

    // public function __construct(interface_basketballplayer $player)
    // {
    //     $this->player = $player;
    // }

    public function __construct(lebron $player)
    {
        $this->player = $player;
    }

    public function play()
    {
        echo $this->player->shot();
    }
}

このクラスをインスタンス化するには、lebronインスタンスを渡さなくてはいけません。
では先ほどと同様にコントローラーの引数にて、StarPlayerを引数に指定するとどうなるでしょうか。

TestController.php一部抜粋
    use App\Test\StarPlayer;
    public function test2(StarPlayer $starPlayer)
    {
        $starPlayer->play();
    }
    //ブラウザに"レブロンのダンクシュート"と表示される

先ほどの例と違う点はコンストラクタが指定されおり、そのコンストラクタにlebronクラスのインスタンスを渡さなければいけない点です。
アクションの引数にクラスのインスタンスを指定するだけでインスタンス化できる点は前例と同じですが、
インスタンス化する際にコンストラクタに指定されたlebronクラスのインスタンス自動で渡されていることが分かります。

これがコンストラクタインジェクションです。
こちらに関してもメソッドインジェクション同様にサービスコンテナが働いています。

先ほどコンストラクタの引数にインターフェースを指定したらどうなるのでしょう。

StarPlayer.php
<?php
namespace App\Test;

class StarPlayer
{
    private $player;

    public function __construct(interface_basketballplayer $player)
    {
        $this->player = $player;
    }

    // public function __construct(lebron $player)
    // {
    //     $this->player = $player;
    // }

    public function play()
    {
        echo $this->player->shot();
    }
}

結論、このままではサービスコンテナは引数にどんなインスタンスを渡せばいいか分かりませんのでエラーが起こります。

インターフェースをインスタンス化することはできないので。

なのでサービスコンテナにこのインターフェースが指定されたら、このクラスのインスタンスを渡してと
教えてあげる必要があります。サービスプロバイダーで。
https://qiita.com/minato-naka/items/afa4b930a2afac23261b

AppServiceProvider.php一部抜粋
$this->app->bind(\App\Test\interface_basketballplayer::class,\App\Test\lebron::class);

コンストラクタの引数にinterface_basketballplayerのインスタンスが指定されていたら、
lebronクラスのインスタンスを渡してあげてとサービスプロバイダーで設定します。するとエラーが消えます。

Discussion