Laravelでのサービスコンテナについて

サービスコンテナについて

役割はLaravelフレームワーク内でのインスタンス管理。

インスタンスの生成方法を登録する処理をバインドと呼ぶ。

指定されたインスタンスを生成して返すことを解決すると呼ぶ。

バインドの定義場所

app\ProvidersフォルダにServiceProviderの子クラスを作成して定義しても良いし、
既に用意されているAppServiceProviderクラスに定義しても良い。

registerメソッドとbootメソッドがあり、アプリケーション起動処理中に実行される。

通常、バインド処理はregisterメソッドに定義されるが、
他のクラスを利用するクラスをバインドする時はbootメソッドで定義するのが良い。
その理由はbootメソッドが実行される時は、他の機能のバインド処理も終了しているため。

コンテナのバインドと解決の例

makeメソッド実行の度にインスタンス生成

下記コードをTinkerで実行すると良い。

use Illuminate\Foundation\Application;

class Number {
    protected $number;

    public function __construct($number = 0) {
        $this->number = $number;
    }

    public function getNumber() {
        return $this->number;
    }
}

// 無名関数の引数は配列である必要がある。配列以外だとエラーになる。
// bindIfメソッドを使うと、既に同じ名前でbindされていた場合には無効となる。
app()->bind(Number::class, function(Application $app, array $args_number) {
    return new Number($args_number[0]);
});

$number = app()->make(Number::class, [100]);
$number->getNumber();

makeメソッドを何度実行しても同一のインスタンスが返される

下記コードをTinkerで実行すると良い。

use Illuminate\Foundation\Application;

class Number {
    protected $number;

    public function __construct() {
        $this->number = mt_rand(0, 100);
    }

    public function getNumber() {
        return $this->number;
    }
}

app()->singleton(Number::class, function(Application $app) {
    return new Number();
});

$number = app()->make(Number::class);
$number2 = app(Number::class);
$number3 = app()->make(Number::class);
$number->getNumber();
$number2->getNumber();
$number3->getNumber();

既に生成したインスタンスを返す

下記コードをTinkerで実行すると良い。

class Number {
    protected $number;

    public function __construct() {
        $this->number = mt_rand(0, 100);
    }

    public function getNumber() {
        return $this->number;
    }
}

$number = new Number();
$number->getNumber();

app()->instance(Number::class, $number);

$number2 = app()->make(Number::class);
$number3 = app(Number::class);
$number2->getNumber();
$number3->getNumber();

バインドされていないクラスのインスタンスを生成

下記コードをTinkerで実行すると良い。

class Number {
    protected $number;

    public function __construct() {
        $this->number = mt_rand(0, 100);
    }

    public function getNumber() {
        return $this->number;
    }
}

$number = app()->make(Number::class);
$number->getNumber();

依存性の注入(Dependency Injection) (バインドの削除や確認方法あり)

コンストラクタインジェクション

下記コードをTinkerで実行すると良い。

interface NotifierInterface {
    public function send(string $to, string $message): void;
}


class MailSender implements NotifierInterface {
    public function send(string $to, string $message): void {
        // メール送信処理        
        echo 'メール送信:' . $to . ' ( ' . $message . ' )';
    }
}


class PushSender implements NotifierInterface {
    public function send(string $to, string $message): void {
        // プッシュ通知処理
        echo 'プッシュ通知:' . $to . ' ( ' . $message . ' )';
    }
}


class UserService {
    protected $notifier;

    public function __construct(NotifierInterface $notifier) {
        $this->notifier = $notifier;
    }

    public function sendNotification(string $to, string $message): void {
        $this->notifier->send($to, $message);
    }
}


app()->bind(NotifierInterface::class, function() {
    return new MailSender();
});

$user = app()->make(UserService::class);
$user->sendNotification('to', 'message');        // メール送信:to ( message ) と表示される。


app()->bind(NotifierInterface::class, function() {
    return new PushSender();
});

$user = app()->make(UserService::class);
$user->sendNotification('to', 'message');        // プッシュ通知:to ( message ) と表示される。


// バインドを削除。
App::offsetUnset(NotifierInterface::class);     // または app()->offsetUnset(NotifierInterface::class); でも良い。
$user = app()->make(UserService::class);        // 次のエラーが表示される。
// Illuminate\Contracts\Container\BindingResolutionException  Target [NotifierInterface] is not instantiable while building [UserService].


// バインドの存在を確認
app()[NotifierInterface::class];
// バインドの存在を確認2
app()->getBindings()[NotifierInterface::class];

※ 抽象クラス(インターフェイス等)に依存させると柔軟に対応できることが多いらしい。

メソッドインジェクション

下記コードをTinkerで実行すると良い。

interface NotifierInterface {
    public function send(string $to, string $message): void;
}


class MailSender implements NotifierInterface {
    public function send(string $to, string $message): void {
        // メール送信処理        
        echo 'メール送信:' . $to . ' ( ' . $message . ' )';
    }
}


class UserService {
    protected $notifier;

    public function __construct(NotifierInterface $notifier) {
        $this->notifier = $notifier;
    }

    public function sendNotification(string $to, string $message): void {
        $this->notifier->send($to, $message);
    }
}


app()->bind(NotifierInterface::class, function() {
    return new MailSender();
});


$service = app(UserService::class);
$to = 'address';
$message = 'message2';
app()->call([$service, 'sendNotification'], ['to' => $to, 'message' => $message]);
// メール送信:address ( message2 ) と表示される。

クラス名に応じて注入するインスタンスを変える

下記コードをTinkerで実行すると良い。

interface NotifierInterface {
    public function send(string $to, string $message): void;
}


class MailSender implements NotifierInterface {
    public function send(string $to, string $message): void {
        // メール送信処理        
        echo 'メール送信:' . $to . ' ( ' . $message . ' )';
    }
}


class PushSender implements NotifierInterface {
    public function send(string $to, string $message): void {
        // プッシュ通知処理
        echo 'プッシュ通知:' . $to . ' ( ' . $message . ' )';
    }
}


class UserService {
    protected $notifier;

    public function __construct(NotifierInterface $notifier) {
        $this->notifier = $notifier;
    }

    public function sendNotification(string $to, string $message): void {
        $this->notifier->send($to, $message);
    }
}


class AdminService {    
    protected $notifier;

    public function __construct(NotifierInterface $notifier) {
        $this->notifier = $notifier;
    }

    public function sendNotification(string $to, string $message): void {
        $this->notifier->send($to, $message);
    }
}

// bindメソッドの代わりに実行すると考えて良い。
app()->when(UserService::class)
    ->needs(NotifierInterface::class)
    ->give(MailSender::class);

app()->when(AdminService::class)
    ->needs(NotifierInterface::class)
    ->give(PushSender::class);

app(UserService::class)->sendNotification('to', 'message');    // メール送信:to ( message )
app(AdminService::class)->sendNotification('to', 'message');   // プッシュ通知:to ( message )

コメント

タイトルとURLをコピーしました