イントロダクション
Laravelサービスコンテナは、クラスの依存関係を管理し、依存注入を実行するための強力なツールです。依存の注入は、本質的にこれを意味する派手なフレーズです。クラスの依存は、コンストラクターまたは場合によっては「セッター」メソッドを介してクラスに「注入」されます。
簡単な例を見てみましょう。
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\Models\User;
class UserController extends Controller
{
/**
* Userリポジトリの実装
*
* @var UserRepository
*/
protected $users;
/**
* 新しいコントローラインスタンスの生成
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* 指定ユーザーのプロファイル表示
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
この例では、UserController
はデータソースからユーザーを取得する必要があります。そのため、ユーザーを取得できるサービスを注入します。このコンテキストでは、UserRepository
はおそらくEloquentを使用してデータベースからユーザー情報を取得します。しかし、リポジトリが挿入されているため、別の実装と簡単に交換可能です。また、アプリケーションをテストするときに、「UserRepository」のダミー実装を簡単に「モック」または作成することもできます。
Laravelサービスコンテナを深く理解することは、強力で大規模なアプリケーションを構築するため、およびLaravelコア自体に貢献するために不可欠です。
設定なしの依存解決
クラスに依存関係がない場合、または他の具象クラス(インターフェイスではない)のみに依存している場合、そのクラスを依存解決する方法をコンテナへ指示する必要はありません。たとえば、以下のコードをroutes/web.php
ファイルに配置できます。
<?php
class Service
{
//
}
Route::get('/', function (Service $service) {
die(get_class($service));
});
この例でアプリケーションの/
ルートを訪問すれば、自動的にService
クラスが依存解決され、ルートのハンドラに依存挿入されます。これは大転換です。これは、アプリケーションの開発において、肥大化する設定ファイルの心配をせず依存注入を利用できることを意味します。
幸いに、Laravelアプリケーションを構築するときに作成するクラスの多くは、コントローラ、イベントリスナ、ミドルウェアなどのhandle
メソッドにより依存関係を注入ができます。設定なしの自動的な依存注入の力を味わったなら、これなしに開発することは不可能だと思うことでしょう。
いつコンテナを使用するか
幸運にも、依存解決の設定がいらないため、ルート、コントローラ、イベントリスナ、その他どこでも、コンテナを手作業で操作しなくても、依存関係を頻繁にタイプヒントできます。たとえば、現在のリクエストに簡単にアクセスできるように、ルート定義でIlluminate\Http\Request
オブジェクトをタイプヒントできます。このコードを書くため、コンテナを操作する必要はありません。コンテナはこうした依存関係の注入をバックグラウンドで管理しています。
use Illuminate\Http\Request;
Route::get('/', function (Request $request) {
// ...
});
多くの場合、自動依存注入とファサードのおかげで、コンテナから手作業でバインドしたり依存解決したりすることなく、Laravelアプリケーションを構築できます。では、いつ手作業でコンテナを操作するのでしょう?2つの状況を調べてみましょう。
第1に、インターフェイスを実装するクラスを作成し、そのインターフェイスをルートまたはクラスコンストラクターで型指定する場合は、コンテナにそのインターフェイスを解決する方法を指示する必要があります。第2に、他のLaravel開発者と共有する予定のLaravelパッケージの作成の場合、パッケージのサービスをコンテナにバインドする必要がある場合があります。
結合
結合の基本
シンプルな結合
ほとんどすべてのサービスコンテナ結合はサービスプロバイダ内で登録されるため、こうした例のほとんどは、この状況でのコンテナの使用法になります。
サービスプロバイダ内では、常に$this->app
プロパティを介してコンテナにアクセスできます。bind
メソッドを使用して結合を登録しできます。登録するクラスまたはインターフェイス名を、クラスのインスタンスを返すクロージャとともに渡します。
use App\Services\Transistor;
use App\Services\PodcastParser;
$this->app->bind(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
リゾルバの引数としてコンテナ自体を受け取ることに注意してください。そのコンテナを使用して、構築中のオブジェクトの依存関係を解決できるのです。
前述のように、通常はサービスプロバイダ内のコンテナの中で操作します。ただし、サービスプロバイダの外部でコンテナとやり取りする場合は、App
ファサードを用いて操作します。
use App\Services\Transistor;
use Illuminate\Support\Facades\App;
App::bind(Transistor::class, function ($app) {
// ...
});
Note: クラスがどのインターフェイスにも依存しない場合、クラスをコンテナにバインドする必要はありません。コンテナは、リフレクションを使用してこれらのオブジェクトを自動的に解決できるため、これらのオブジェクトの作成方法を指示する必要はありません。
シングルトンの結合
singleton
メソッドは、クラスまたはインターフェイスをコンテナにバインドしますが、これは1回のみ依存解決される必要がある結合です。シングルトン結合が依存解決されたら、コンテナに対する後続の呼び出しで、同じオブジェクトインスタンスが返されます。
use App\Services\Transistor;
use App\Services\PodcastParser;
$this->app->singleton(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
スコープ付きシングルトンの結合
scoped
メソッドは、Laravelのリクエストやジョブのライフサイクルの中で、一度だけ解決されるべきクラスやインターフェイスをコンテナへ結合します。このメソッドはsingleton
メソッドと似ていますが、scoped
メソッドを使って登録したインスタンスは、Laravelアプリケーションが新しい「ライフサイクル」を開始するたびにフラッシュされます。例えば、Laravel
Octaneワーカが新しいリクエストを処理するときや、Laravel キューワーカが新しいジョブを処理するときなどです。
use App\Services\Transistor;
use App\Services\PodcastParser;
$this->app->scoped(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
インスタンスの結合
instance
メソッドを使用して、既存のオブジェクトインスタンスをコンテナへ結合することもできます。指定したインスタンスは、コンテナに対する後続の呼び出しで常に返されます。
use App\Services\Transistor;
use App\Services\PodcastParser;
$service = new Transistor(new PodcastParser);
$this->app->instance(Transistor::class, $service);
インターフェイスと実装の結合
サービスコンテナの非常に強力な機能は、インターフェイスを特定の実装に結合する機能です。たとえば、EventPusher
インターフェイスとRedisEventPusher
実装があると仮定しましょう。このインターフェイスのRedisEventPusher
実装をコーディングしたら、次のようにサービスコンテナに登録できます。
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;
$this->app->bind(EventPusher::class, RedisEventPusher::class);
この文は、クラスがEventPusher
の実装を必要とするときに、RedisEventPusher
を注入する必要があることをコンテナに伝えています。これで、コンテナにより依存解決されるクラスのコンストラクタでEventPusher
インターフェイスをタイプヒントできます。Laravelアプリケーション内のコントローラ、イベントリスナ、ミドルウェア、およびその他のさまざまなタイプのクラスは、常にコンテナを使用して解決されることを忘れないでください。
use App\Contracts\EventPusher;
/**
* 新しいクラスインスタンスの生成
*
* @param \App\Contracts\EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
コンテキストによる結合
同じインターフェイスを利用する2つのクラスがある場合、各クラスに異なる実装を依存注入したい場合があります。たとえば、2つのコントローラは、Illuminate\Contracts\Filesystem\Filesystem
契約の異なる実装に依存する場合があります。Laravelは、この動作を定義するためのシンプルで流暢なインターフェイスを提供します。
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
プリミティブの結合
注入されたクラスを受け取るだけでなく、整数などのプリミティブ値も注入され、受け取るクラスがときにはあるでしょう。コンテキストによる結合を使用して、クラスへ必要な値を簡単に依存注入できます。
use App\Http\Controllers\UserController;
$this->app->when(UserController::class)
->needs('$variableName')
->give($value);
クラスがタグ付きインスタンスの配列へ依存する場合があります。giveTagged
メソッドを使用すると、そのタグを使用してすべてのコンテナバインディングを簡単に挿入できます。
$this->app->when(ReportAggregator::class)
->needs('$reports')
->giveTagged('reports');
アプリケーションの設定ファイルの1つから値を注入する必要がある場合は、giveConfig
メソッドを使用します。
$this->app->when(ReportAggregator::class)
->needs('$timezone')
->giveConfig('app.timezone');
型指定した可変引数の結合
時折、可変コンストラクター引数を使用して型付きオブジェクトの配列を受け取るクラスが存在する場合があります。
<?php
use App\Models\Filter;
use App\Services\Logger;
class Firewall
{
/**
* ロガーインスタンス
*
* @var \App\Services\Logger
*/
protected $logger;
/**
* フィルタインスタンス
*
* @var array
*/
protected $filters;
/**
* 新しいクラスインスタンスの生成
*
* @param \App\Services\Logger $logger
* @param array $filters
* @return void
*/
public function __construct(Logger $logger, Filter ...$filters)
{
$this->logger = $logger;
$this->filters = $filters;
}
}
文脈による結合を使用すると、依存解決したFilter
インスタンスの配列を返すクロージャをgive
メソッドへ渡すことで、この依存関係を解決できます。
$this->app->when(Firewall::class)
->needs(Filter::class)
->give(function ($app) {
return [
$app->make(NullFilter::class),
$app->make(ProfanityFilter::class),
$app->make(TooLongFilter::class),
];
});
利便性のため、いつでもFirewall
がFilter
インスタンスを必要とするときは、コンテナが解決するクラス名の配列も渡せます。
$this->app->when(Firewall::class)
->needs(Filter::class)
->give([
NullFilter::class,
ProfanityFilter::class,
TooLongFilter::class,
]);
可変引数タグの依存
クラスには、特定のクラスとしてタイプヒントされた可変引数の依存関係を持つ場合があります(Report ...$reports
)。needs
メソッドとgiveTagged
メソッドを使用すると、特定の依存関係に対して、そのtagを使用してすべてのコンテナ結合を簡単に挿入できます。
$this->app->when(ReportAggregator::class)
->needs(Report::class)
->giveTagged('reports');
タグ付け
場合により、特定の結合「カテゴリ」をすべて依存解決する必要が起きます。たとえば、さまざまなReport
インターフェイス実装の配列を受け取るレポートアナライザを構築しているとしましょう。Report
実装を登録した後、tag
メソッドを使用してそれらにタグを割り当てられます。
$this->app->bind(CpuReport::class, function () {
//
});
$this->app->bind(MemoryReport::class, function () {
//
});
$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');
サービスにタグ付けしたら、コンテナのtagged
メソッドを使用して簡単にすべてを依存解決できます。
$this->app->bind(ReportAnalyzer::class, function ($app) {
return new ReportAnalyzer($app->tagged('reports'));
});
結合の拡張
extend
メソッドを使用すると、依存解決済みのサービスを変更できます。たとえば、サービスを依存解決した後、追加のコードを実行してサービスをデコレートまたは設定できます。extend
メソッドは拡張するサービスクラスと、変更したサービスを返すクロージャの2引数を取ります。クロージャは解決するサービスとコンテナインスタンスを引数に取ります。
$this->app->extend(Service::class, function ($service, $app) {
return new DecoratedService($service);
});
依存解決
make
メソッド
make
メソッドを使用して、コンテナからクラスインスタンスを解決します。make
メソッドは、解決したいクラスまたはインターフェイスの名前を受け入れます。
use App\Services\Transistor;
$transistor = $this->app->make(Transistor::class);
クラスの依存関係の一部がコンテナを介して解決できない場合は、それらを連想配列としてmakeWith
メソッドに渡すことでそれらを依存注入できます。たとえば、Transistor
サービスに必要な$id
コンストラクタ引数を手作業で渡すことができます。
use App\Services\Transistor;
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);
サービスプロバイダの外部で、$app
変数にアクセスできないコードの場所では、App
ファサード、app
ヘルパを使用してコンテナからクラスインスタンスを依存解決します。
use App\Services\Transistor;
use Illuminate\Support\Facades\App;
$transistor = App::make(Transistor::class);
$transistor = app(Transistor::class);
Laravelコンテナインスタンス自体をコンテナにより解決中のクラスへ依存注入したい場合は、クラスのコンストラクタでIlluminate\Container\Container
クラスを入力してください。
use Illuminate\Container\Container;
/**
* 新しいクラスインスタンスの生成
*
* @param \Illuminate\Container\Container $container
* @return void
*/
public function __construct(Container $container)
{
$this->container = $container;
}
自動注入
あるいは、そして重要なことに、コントローラ、イベントリスナ、ミドルウェアなど、コンテナにより解決されるクラスのコンストラクターでは、依存関係をタイプヒントすることができます。さらに、キュー投入するジョブのhandle
メソッドでも、依存関係をタイプヒントできます。実践的に、ほとんどのオブジェクトはコンテナにより解決されるべきでしょう。
たとえば、コントローラのコンストラクタでアプリケーションが定義したリポジトリをタイプヒントすることができます。リポジトリは自動的に解決され、クラスに依存注入されます。
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* Userリポジトリインスタンス
*
* @var \App\Repositories\UserRepository
*/
protected $users;
/**
* 新しいコントローラインスタンスの生成
*
* @param \App\Repositories\UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* 指定したIDのユーザーを表示
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
}
メソッドの起動と依存注入
コンテナがあるメソッドの依存関係を自動的に注入できるようにしたまま、そのオブジェクトインスタンス上のメソッドを起動したい場合があります。たとえば、以下のクラスを想定してください。
<?php
namespace App;
use App\Repositories\UserRepository;
class UserReport
{
/**
* 新しいユーザーレポートの生成
*
* @param \App\Repositories\UserRepository $repository
* @return array
*/
public function generate(UserRepository $repository)
{
// ...
}
}
次のように、コンテナを介してgenerate
メソッドを呼び出せます。
use App\UserReport;
use Illuminate\Support\Facades\App;
$report = App::call([new UserReport, 'generate']);
call
メソッドは任意のPHP
callableを受け入れます。コンテナのcall
メソッドは、依存関係を自動的に注入しながら、クロージャを呼び出すためにも使用できます。
use App\Repositories\UserRepository;
use Illuminate\Support\Facades\App;
$result = App::call(function (UserRepository $repository) {
// ...
});
コンテナイベント
サービスコンテナは、オブジェクトを依存解決するたびにイベントを発生させます。resolving
メソッドを使用してこのイベントをリッスンできます。
use App\Services\Transistor;
$this->app->resolving(Transistor::class, function ($transistor, $app) {
// コンテナがTransistorタイプのオブジェクトを解決するときに呼び出される
});
$this->app->resolving(function ($object, $app) {
// コンテナが任意のタイプのオブジェクトを解決するときに呼び出される
});
ご覧のとおり、解決しているオブジェクトがコールバックに渡され、利用側に渡される前にオブジェクトへ追加のプロパティを設定できます。
PSR-11
Laravelのサービスコンテナは、PSR-11インターフェイスを実装しています。したがって、PSR-11コンテナインターフェイスをタイプヒントして、Laravelコンテナのインスタンスを取得できます。
use App\Services\Transistor;
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get(Transistor::class);
//
});
指定した識別子を解決できない場合は例外を投げます。識別子が結合されなかった場合、例外はPsr\Container\NotFoundExceptionInterface
のインスタンスです。識別子が結合されているが依存解決できなかった場合、Psr\Container\ContainerExceptionInterface
のインスタンスを投げます。