サービスコンテナについて
役割は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 )