メインコンテンツまでスキップ

サービスコンテナ

はじめに

Laravelのサービスコンテナは、クラスの依存関係を管理し、依存性の注入を行うための強力なツールです。依存性の注入とは、クラスの依存関係がコンストラクタや一部の場合には"setter"メソッドを介してクラスに「注入」されるということを意味する洒落たフレーズです。

簡単な例を見てみましょう:

    <?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;
use Illuminate\View\View;

class UserController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected UserRepository $users,
) {}

/**
* Show the profile for the given user.
*/
public function show(string $id): View
{
$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($service::class);
});

この例では、アプリケーションの / ルートにアクセスすると、Service クラスが自動的に解決され、ルートのハンドラにインジェクトされます。これは画期的です。これにより、アプリケーションを開発し、依存性注入を利用することができ、膨大な設定ファイルを気にする必要がなくなります。

幸いなことに、Laravel アプリケーションを構築する際に書くことになる多くのクラスは、コントローライベントリスナーミドルウェアなど、コンテナ経由で依存関係を自動的に受け取ります。さらに、キューに入れられたジョブhandle メソッドで依存関係を型ヒントすることもできます。自動かつゼロ構成の依存性注入の力を味わうと、それなしで開発することは不可能に感じられるでしょう。

コンテナを利用するタイミング

ゼロ構成の解決のおかげで、ルート、コントローラ、イベントリスナーなどで依存関係を型ヒントすることがよくありますが、コンテナと直接やり取りすることなく行います。例えば、Illuminate\Http\Request オブジェクトをルート定義で型ヒントすることで、現在のリクエストに簡単にアクセスできるようにすることができます。このコードを書く際にコンテナと直接やり取りする必要はありませんが、これらの依存関係のインジェクションを裏で管理しています:

    use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
// ...
});

多くの場合、自動的な依存性注入と ファサードのおかげで、Laravel アプリケーションを一切コンテナから手動でバインドしたり解決したりすることなく構築することができます。では、いつコンテナと手動でやり取りする必要があるのでしょうか? 2つの状況を見てみましょう。

まず、インターフェースを実装するクラスを書き、そのインターフェースをルートやクラスのコンストラクタで型ヒントしたい場合、コンテナにそのインターフェースを解決する方法を伝える必要があります。次に、他の Laravel 開発者と共有する意図のあるLaravel パッケージを書いている場合、パッケージのサービスをコンテナにバインドする必要があるかもしれません。

バインディング

バインディングの基本

シンプルなバインディング

ほとんどのサービスコンテナのバインディングは、サービスプロバイダ内で登録されます。そのため、これらの例のほとんどは、そのコンテキストでコンテナを使用する方法を示します。

サービスプロバイダ内では、常に$this->appプロパティを介してコンテナにアクセスできます。bindメソッドを使用してバインディングを登録できます。登録したいクラスまたはインターフェース名と、そのクラスのインスタンスを返すクロージャを渡します:

    use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});

リゾルバにコンテナ自体が引数として渡されることに注意してください。その後、オブジェクトを構築する際のサブ依存関係を解決するためにコンテナを使用できます。

前述のように、通常はサービスプロバイダ内でコンテナとやり取りすることになります。ただし、サービスプロバイダの外部でコンテナとやり取りしたい場合は、App ファサードを介して行うことができます:

    use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
// ...
});

bindIfメソッドを使用して、指定されたタイプのバインディングがまだ登録されていない場合にのみコンテナバインディングを登録できます:

$this->app->bindIf(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
注記

インターフェースに依存しないクラスをコンテナにバインドする必要はありません。これらのオブジェクトを構築する方法をコンテナに指示する必要はありません。コンテナはリフレクションを使用してこれらのオブジェクトを自動的に解決できます。

シングルトンのバインディング

singletonメソッドは、1回だけ解決されるべきクラスまたはインターフェースをコンテナにバインドします。シングルトンバインディングが解決されると、そのコンテナへの後続の呼び出しで同じオブジェクトインスタンスが返されます:

    use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});

singletonIfメソッドを使用して、指定されたタイプのバインディングがまだ登録されていない場合にのみシングルトンコンテナバインディングを登録できます:

$this->app->singletonIf(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});

スコープ付きシングルトンのバインディング

scoped メソッドは、特定の Laravel リクエスト / ジョブライフサイクル内で一度だけ解決されるべきクラスまたはインターフェースをコンテナにバインドします。このメソッドは singleton メソッドに似ていますが、scoped メソッドを使用して登録されたインスタンスは、Laravel アプリケーションが新しい "ライフサイクル" を開始するたびにフラッシュされます。たとえば、Laravel Octane ワーカーが新しいリクエストを処理するときや、Laravel queue worker が新しいジョブを処理するときなどです:

    use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->scoped(Transistor::class, function (Application $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);

インターフェースの実装へのバインディング

サービスコンテナの非常に強力な機能の1つは、インターフェースを特定の実装にバインドする能力です。たとえば、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;

/**
* Create a new class instance.
*/
public function __construct(
protected EventPusher $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');

アプリケーションの設定ファイルから値をインジェクトする必要がある場合は、giveConfig メソッドを使用できます:

    $this->app->when(ReportAggregator::class)
->needs('$timezone')
->giveConfig('app.timezone');

型付き可変引数のバインディング

時折、可変引数コンストラクタ引数を使用して、型付きオブジェクトの配列を受け取るクラスがあるかもしれません:

    <?php

use App\Models\Filter;
use App\Services\Logger;

class Firewall
{
/**
* The filter instances.
*
* @var array
*/
protected $filters;

/**
* Create a new class instance.
*/
public function __construct(
protected Logger $logger,
Filter ...$filters,
) {
$this->filters = $filters;
}
}

コンテキストバインディングを使用すると、give メソッドに、解決された Filter インスタンスの配列を返すクロージャを提供することで、この依存関係を解決できます:

    $this->app->when(Firewall::class)
->needs(Filter::class)
->give(function (Application $app) {
return [
$app->make(NullFilter::class),
$app->make(ProfanityFilter::class),
$app->make(TooLongFilter::class),
];
});

便宜上、FirewallFilter インスタンスを必要とするときに、コンテナが解決するクラス名の配列を提供することもできます:

    $this->app->when(Firewall::class)
->needs(Filter::class)
->give([
NullFilter::class,
ProfanityFilter::class,
TooLongFilter::class,
]);

可変タグ依存関係

あるクラスが、指定されたクラス (Report ...$reports) として型ヒントされた可変依存関係を持つ場合があります。needs メソッドと giveTagged メソッドを使用すると、その依存関係のためにコンテナバインディングをすべて簡単にインジェクトできます:

    $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 (Application $app) {
return new ReportAnalyzer($app->tagged('reports'));
});

バインディングの拡張

extend メソッドを使用すると、解決されたサービスを変更できます。たとえば、サービスが解決されたときに、サービスをデコレートしたり構成したりするための追加のコードを実行できます。extend メソッドは、拡張しているサービスクラスと、変更されたサービスを返すクロージャの2つの引数を受け入れます。クロージャは、解決されているサービスとコンテナインスタンスを受け取ります:

    $this->app->extend(Service::class, function (Service $service, Application $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]);

bound メソッドは、クラスまたはインターフェースがコンテナに明示的にバインドされているかどうかを判定するために使用できます:

    if ($this->app->bound(Transistor::class)) {
// ...
}

サービスプロバイダの外部で、$app 変数にアクセスできないコードの場所で、App ファサード または app ヘルパー を使用して、コンテナからクラスのインスタンスを解決できます:

    use App\Services\Transistor;
use Illuminate\Support\Facades\App;

$transistor = App::make(Transistor::class);

$transistor = app(Transistor::class);

コンテナ自体をクラスにインジェクトする場合は、クラスのコンストラクタで Illuminate\Container\Container クラスを型ヒントすることができます:

    use Illuminate\Container\Container;

/**
* Create a new class instance.
*/
public function __construct(
protected Container $container
) {}

自動インジェクション

代替として、重要なのは、コンテナによって解決されるクラスのコンストラクタで依存関係を型ヒントすることができます。これには、コントローライベントリスナーミドルウェアなどが含まれます。さらに、キューに入れられたジョブhandle メソッドで依存関係を型ヒントすることもできます。実際には、ほとんどのオブジェクトはコンテナによって解決されるべきです。


たとえば、コントローラのコンストラクタでアプリケーションで定義されたリポジトリを型ヒントすることができます。リポジトリは自動的に解決され、クラスにインジェクトされます:

```php
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;
use App\Models\User;

class UserController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected UserRepository $users,
) {}

/**
* Show the user with the given ID.
*/
public function show(string $id): User
{
$user = $this->users->findOrFail($id);

return $user;
}
}

メソッドの呼び出しとインジェクション

時々、コンテナにそのメソッドの依存関係を自動的にインジェクトさせながら、オブジェクトインスタンス上でメソッドを呼び出したいと思うことがあります。たとえば、次のクラスが与えられた場合:

    <?php

namespace App;

use App\Repositories\UserRepository;

class UserReport
{
/**
* Generate a new user report.
*/
public function generate(UserRepository $repository): array
{
return [
// ...
];
}
}

次のようにコンテナを介して 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;
use Illuminate\Contracts\Foundation\Application;

$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
// Called when container resolves objects of type "Transistor"...
});

$this->app->resolving(function (mixed $object, Application $app) {
// Called when container resolves object of any type...
});

解決されるオブジェクトがコールバックに渡されるため、そのコンシューマに渡される前にオブジェクトに追加のプロパティを設定することができます。

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 のインスタンスがスローされます。