はじめに

Laravelのサービスコンテナは、クラスの依存関係を管理し、依存性注入を行うための強力なツールです。依存性注入とは、基本的に次のような意味を持つ言葉です:クラスの依存関係は、コンストラクタまたは場合によっては「セッター」メソッドを介してクラスに「注入」されます。

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

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Services\AppleMusic;
  4. use Illuminate\View\View;
  5. class PodcastController extends Controller
  6. {
  7. /**
  8. * Create a new controller instance.
  9. */
  10. public function __construct(
  11. protected AppleMusic $apple,
  12. ) {}
  13. /**
  14. * Show information about the given podcast.
  15. */
  16. public function show(string $id): View
  17. {
  18. return view('podcasts.show', [
  19. 'podcast' => $this->apple->findPodcast($id)
  20. ]);
  21. }
  22. }

この例では、PodcastControllerはApple Musicなどのデータソースからポッドキャストを取得する必要があります。したがって、ポッドキャストを取得できるサービスを注入します。サービスが注入されるため、アプリケーションをテストする際にAppleMusicサービスのダミー実装を簡単に「モック」することができます。

Laravelのサービスコンテナを深く理解することは、強力で大規模なアプリケーションを構築するために不可欠であり、Laravelコア自体に貢献するためにも重要です。

ゼロ構成解決

クラスに依存関係がない場合、または他の具体的なクラス(インターフェースではない)にのみ依存している場合、コンテナはそのクラスを解決する方法を指示する必要はありません。たとえば、次のコードをroutes/web.phpファイルに配置することができます:

  1. <?php
  2. class Service
  3. {
  4. // ...
  5. }
  6. Route::get('/', function (Service $service) {
  7. die($service::class);
  8. });

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

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

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

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

  1. use Illuminate\Http\Request;
  2. Route::get('/', function (Request $request) {
  3. // ...
  4. });

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

まず、インターフェースを実装するクラスを書いて、そのインターフェースをルートまたはクラスのコンストラクタで型ヒントしたい場合、そのインターフェースを解決する方法をコンテナに指示する必要があります。次に、他のLaravel開発者と共有する予定のLaravelパッケージを作成している場合、パッケージのサービスをコンテナにバインドする必要があります。

バインディング

バインディングの基本

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

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

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

  1. use App\Services\Transistor;
  2. use App\Services\PodcastParser;
  3. use Illuminate\Contracts\Foundation\Application;
  4. $this->app->bind(Transistor::class, function (Application $app) {
  5. return new Transistor($app->make(PodcastParser::class));
  6. });

解決者に引数としてコンテナ自体を受け取ることに注意してください。これにより、構築しているオブジェクトのサブ依存関係を解決するためにコンテナを使用できます。

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

  1. use App\Services\Transistor;
  2. use Illuminate\Contracts\Foundation\Application;
  3. use Illuminate\Support\Facades\App;
  4. App::bind(Transistor::class, function (Application $app) {
  5. // ...
  6. });

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

  1. $this->app->bindIf(Transistor::class, function (Application $app) {
  2. return new Transistor($app->make(PodcastParser::class));
  3. });

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

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

  1. ``````php
  2. use App\Services\Transistor;
  3. use App\Services\PodcastParser;
  4. use Illuminate\Contracts\Foundation\Application;
  5. $this->app->singleton(Transistor::class, function (Application $app) {
  6. return new Transistor($app->make(PodcastParser::class));
  7. });
  8. `
  1. ``````php
  2. $this->app->singletonIf(Transistor::class, function (Application $app) {
  3. return new Transistor($app->make(PodcastParser::class));
  4. });
  5. `

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

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

  1. use App\Services\Transistor;
  2. use App\Services\PodcastParser;
  3. use Illuminate\Contracts\Foundation\Application;
  4. $this->app->scoped(Transistor::class, function (Application $app) {
  5. return new Transistor($app->make(PodcastParser::class));
  6. });

インスタンスのバインディング

既存のオブジェクトインスタンスをinstanceメソッドを使用してコンテナにバインドすることもできます。指定されたインスタンスは、コンテナへの後続の呼び出しで常に返されます:

  1. use App\Services\Transistor;
  2. use App\Services\PodcastParser;
  3. $service = new Transistor(new PodcastParser);
  4. $this->app->instance(Transistor::class, $service);

インターフェースを実装にバインドする

サービスコンテナの非常に強力な機能は、インターフェースを特定の実装にバインドする能力です。たとえば、EventPusherインターフェースとRedisEventPusher実装があると仮定しましょう。このインターフェースのRedisEventPusher実装をコーディングしたら、次のようにサービスコンテナに登録できます:

  1. use App\Contracts\EventPusher;
  2. use App\Services\RedisEventPusher;
  3. $this->app->bind(EventPusher::class, RedisEventPusher::class);

このステートメントは、クラスがEventPusherの実装を必要とする場合、RedisEventPusherを注入する必要があることをコンテナに指示します。これで、コンテナによって解決されるクラスのコンストラクタでEventPusherインターフェースを型ヒントできます。コントローラー、イベントリスナー、ミドルウェア、Laravelアプリケーション内のさまざまな他のタイプのクラスは、常にコンテナを使用して解決されます:

  1. use App\Contracts\EventPusher;
  2. /**
  3. * Create a new class instance.
  4. */
  5. public function __construct(
  6. protected EventPusher $pusher,
  7. ) {}

コンテキストバインディング

時には、同じインターフェースを利用する2つのクラスがあり、それぞれのクラスに異なる実装を注入したい場合があります。たとえば、2つのコントローラーがIlluminate\Contracts\Filesystem\Filesystem 契約の異なる実装に依存している場合です。Laravelは、この動作を定義するためのシンプルで流暢なインターフェースを提供します:

  1. use App\Http\Controllers\PhotoController;
  2. use App\Http\Controllers\UploadController;
  3. use App\Http\Controllers\VideoController;
  4. use Illuminate\Contracts\Filesystem\Filesystem;
  5. use Illuminate\Support\Facades\Storage;
  6. $this->app->when(PhotoController::class)
  7. ->needs(Filesystem::class)
  8. ->give(function () {
  9. return Storage::disk('local');
  10. });
  11. $this->app->when([VideoController::class, UploadController::class])
  12. ->needs(Filesystem::class)
  13. ->give(function () {
  14. return Storage::disk('s3');
  15. });

コンテキスト属性

コンテキストバインディングは、ドライバーや構成値の実装を注入するために使用されることが多いため、Laravelは、サービスプロバイダーで手動でコンテキストバインディングを定義することなく、これらのタイプの値を注入するためのさまざまなコンテキストバインディング属性を提供します。

たとえば、Storage属性を使用して特定のストレージディスクを注入できます:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Container\Attributes\Storage;
  4. use Illuminate\Contracts\Filesystem\Filesystem;
  5. class PhotoController extends Controller
  6. {
  7. public function __construct(
  8. #[Storage('local')] protected Filesystem $filesystem
  9. )
  10. {
  11. // ...
  12. }
  13. }
  1. ``````php
  2. <?php
  3. namespace App\Http\Controllers;
  4. use Illuminate\Container\Attributes\Auth;
  5. use Illuminate\Container\Attributes\Cache;
  6. use Illuminate\Container\Attributes\Config;
  7. use Illuminate\Container\Attributes\DB;
  8. use Illuminate\Container\Attributes\Log;
  9. use Illuminate\Container\Attributes\Tag;
  10. use Illuminate\Contracts\Auth\Guard;
  11. use Illuminate\Contracts\Cache\Repository;
  12. use Illuminate\Contracts\Database\Connection;
  13. use Psr\Log\LoggerInterface;
  14. class PhotoController extends Controller
  15. {
  16. public function __construct(
  17. #[Auth('web')] protected Guard $auth,
  18. #[Cache('redis')] protected Repository $cache,
  19. #[Config('app.timezone')] protected string $timezone,
  20. #[DB('mysql')] protected Connection $connection,
  21. #[Log('daily')] protected LoggerInterface $log,
  22. #[Tag('reports')] protected iterable $reports,
  23. )
  24. {
  25. // ...
  26. }
  27. }
  28. `

さらに、Laravelは、現在認証されているユーザーを特定のルートまたはクラスに注入するためのCurrentUser属性を提供します:

  1. use App\Models\User;
  2. use Illuminate\Container\Attributes\CurrentUser;
  3. Route::get('/user', function (#[CurrentUser] User $user) {
  4. return $user;
  5. })->middleware('auth');

カスタム属性の定義

独自のコンテキスト属性を作成するには、Illuminate\Contracts\Container\ContextualAttribute契約を実装します。コンテナは、属性のresolveメソッドを呼び出し、そのメソッドは属性を利用するクラスに注入されるべき値を解決する必要があります。以下の例では、Laravelの組み込みConfig属性を再実装します:

  1. <?php
  2. namespace App\Attributes;
  3. use Illuminate\Contracts\Container\ContextualAttribute;
  4. #[Attribute(Attribute::TARGET_PARAMETER)]
  5. class Config implements ContextualAttribute
  6. {
  7. /**
  8. * Create a new attribute instance.
  9. */
  10. public function __construct(public string $key, public mixed $default = null)
  11. {
  12. }
  13. /**
  14. * Resolve the configuration value.
  15. *
  16. * @param self $attribute
  17. * @param \Illuminate\Contracts\Container\Container $container
  18. * @return mixed
  19. */
  20. public static function resolve(self $attribute, Container $container)
  21. {
  22. return $container->make('config')->get($attribute->key, $attribute->default);
  23. }
  24. }

プリミティブのバインディング

時には、いくつかの注入されたクラスを受け取るクラスがあり、整数などの注入されたプリミティブ値も必要とする場合があります。コンテキストバインディングを使用して、クラスが必要とする任意の値を簡単に注入できます:

  1. use App\Http\Controllers\UserController;
  2. $this->app->when(UserController::class)
  3. ->needs('$variableName')
  4. ->give($value);

時には、クラスがタグ付けされたインスタンスの配列に依存することがあります。giveTaggedメソッドを使用して、そのタグを持つすべてのコンテナバインディングを簡単に注入できます:

  1. $this->app->when(ReportAggregator::class)
  2. ->needs('$reports')
  3. ->giveTagged('reports');

アプリケーションの構成ファイルの値を注入する必要がある場合は、giveConfigメソッドを使用できます:

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

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

時には、型付きオブジェクトの配列を可変引数コンストラクタ引数として受け取るクラスがあります:

  1. <?php
  2. use App\Models\Filter;
  3. use App\Services\Logger;
  4. class Firewall
  5. {
  6. /**
  7. * The filter instances.
  8. *
  9. * @var array
  10. */
  11. protected $filters;
  12. /**
  13. * Create a new class instance.
  14. */
  15. public function __construct(
  16. protected Logger $logger,
  17. Filter ...$filters,
  18. ) {
  19. $this->filters = $filters;
  20. }
  21. }

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

  1. $this->app->when(Firewall::class)
  2. ->needs(Filter::class)
  3. ->give(function (Application $app) {
  4. return [
  5. $app->make(NullFilter::class),
  6. $app->make(ProfanityFilter::class),
  7. $app->make(TooLongFilter::class),
  8. ];
  9. });

便利なことに、FirewallFilterインスタンスを必要とするたびに、解決されるクラス名の配列を提供することもできます:

  1. $this->app->when(Firewall::class)
  2. ->needs(Filter::class)
  3. ->give([
  4. NullFilter::class,
  5. ProfanityFilter::class,
  6. TooLongFilter::class,
  7. ]);

可変引数タグ依存関係

時には、クラスが特定のクラス(Report ...$reports)として型ヒントされた可変引数依存関係を持つことがあります。needsおよびgiveTaggedメソッドを使用して、特定の依存関係に対してそのタグを持つすべてのコンテナバインディングを簡単に注入できます:

  1. $this->app->when(ReportAggregator::class)
  2. ->needs(Report::class)
  3. ->giveTagged('reports');

タグ付け

時には、特定の「カテゴリ」のバインディングをすべて解決する必要があります。たとえば、さまざまなReportインターフェースの実装の配列を受け取るレポートアナライザーを構築している場合、Report実装を登録した後、tagメソッドを使用してタグを割り当てることができます:

  1. $this->app->bind(CpuReport::class, function () {
  2. // ...
  3. });
  4. $this->app->bind(MemoryReport::class, function () {
  5. // ...
  6. });
  7. $this->app->tag([CpuReport::class, MemoryReport::class], 'reports');

サービスにタグが付けられたら、コンテナのtaggedメソッドを介してそれらをすべて簡単に解決できます:

  1. $this->app->bind(ReportAnalyzer::class, function (Application $app) {
  2. return new ReportAnalyzer($app->tagged('reports'));
  3. });

バインディングの拡張

  1. ``````php
  2. $this->app->extend(Service::class, function (Service $service, Application $app) {
  3. return new DecoratedService($service);
  4. });
  5. `

解決

makeメソッド

  1. ``````php
  2. use App\Services\Transistor;
  3. $transistor = $this->app->make(Transistor::class);
  4. `

クラスの依存関係の一部がコンテナを介して解決できない場合、makeWithメソッドに連想配列として渡すことで注入できます。たとえば、Transistorサービスによって必要とされる$idコンストラクタ引数を手動で渡すことができます:

  1. use App\Services\Transistor;
  2. $transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);
  1. ``````php
  2. if ($this->app->bound(Transistor::class)) {
  3. // ...
  4. }
  5. `

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

  1. use App\Services\Transistor;
  2. use Illuminate\Support\Facades\App;
  3. $transistor = App::make(Transistor::class);
  4. $transistor = app(Transistor::class);

Laravelコンテナインスタンス自体をコンテナによって解決されるクラスに注入したい場合は、クラスのコンストラクタでIlluminate\Container\Containerクラスを型ヒントすることができます:

  1. use Illuminate\Container\Container;
  2. /**
  3. * Create a new class instance.
  4. */
  5. public function __construct(
  6. protected Container $container,
  7. ) {}

自動注入

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

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

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Services\AppleMusic;
  4. class PodcastController extends Controller
  5. {
  6. /**
  7. * Create a new controller instance.
  8. */
  9. public function __construct(
  10. protected AppleMusic $apple,
  11. ) {}
  12. /**
  13. * Show information about the given podcast.
  14. */
  15. public function show(string $id): Podcast
  16. {
  17. return $this->apple->findPodcast($id);
  18. }
  19. }

メソッドの呼び出しと注入

時には、オブジェクトインスタンスのメソッドを呼び出したいが、そのメソッドの依存関係をコンテナが自動的に注入できるようにしたい場合があります。たとえば、次のクラスがあるとします:

  1. <?php
  2. namespace App;
  3. use App\Services\AppleMusic;
  4. class PodcastStats
  5. {
  6. /**
  7. * Generate a new podcast stats report.
  8. */
  9. public function generate(AppleMusic $apple): array
  10. {
  11. return [
  12. // ...
  13. ];
  14. }
  15. }
  1. ``````php
  2. use App\PodcastStats;
  3. use Illuminate\Support\Facades\App;
  4. $stats = App::call([new PodcastStats, 'generate']);
  5. `
  1. ``````php
  2. use App\Services\AppleMusic;
  3. use Illuminate\Support\Facades\App;
  4. $result = App::call(function (AppleMusic $apple) {
  5. // ...
  6. });
  7. `

コンテナイベント

サービスコンテナは、オブジェクトを解決するたびにイベントを発火します。このイベントをresolvingメソッドを使用してリッスンできます:

  1. use App\Services\Transistor;
  2. use Illuminate\Contracts\Foundation\Application;
  3. $this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
  4. // Called when container resolves objects of type "Transistor"...
  5. });
  6. $this->app->resolving(function (mixed $object, Application $app) {
  7. // Called when container resolves object of any type...
  8. });

解決されるオブジェクトはコールバックに渡され、消費者に渡される前にオブジェクトに追加のプロパティを設定できます。

PSR-11

Laravelのサービスコンテナは、PSR-11インターフェースを実装しています。したがって、PSR-11コンテナインターフェースを型ヒントして、Laravelコンテナのインスタンスを取得できます:

  1. use App\Services\Transistor;
  2. use Psr\Container\ContainerInterface;
  3. Route::get('/', function (ContainerInterface $container) {
  4. $service = $container->get(Transistor::class);
  5. // ...
  6. });

指定された識別子が解決できない場合、例外がスローされます。識別子がバインドされていなかった場合、例外はPsr\Container\NotFoundExceptionInterfaceのインスタンスになります。識別子がバインドされていたが解決できなかった場合、Psr\Container\ContainerExceptionInterfaceのインスタンスがスローされます。