はじめに
Laravelのサービスコンテナは、クラスの依存関係を管理し、依存性注入を行うための強力なツールです。依存性注入とは、基本的に次のような意味を持つ言葉です:クラスの依存関係は、コンストラクタまたは場合によっては「セッター」メソッドを介してクラスに「注入」されます。
簡単な例を見てみましょう:
<?php
namespace App\Http\Controllers;
use App\Services\AppleMusic;
use Illuminate\View\View;
class PodcastController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected AppleMusic $apple,
) {}
/**
* Show information about the given podcast.
*/
public function show(string $id): View
{
return view('podcasts.show', [
'podcast' => $this->apple->findPodcast($id)
]);
}
}
この例では、PodcastController
はApple Musicなどのデータソースからポッドキャストを取得する必要があります。したがって、ポッドキャストを取得できるサービスを注入します。サービスが注入されるため、アプリケーションをテストする際にAppleMusic
サービスのダミー実装を簡単に「モック」することができます。
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));
});
インターフェースに依存しないクラスをコンテナにバインドする必要はありません。これらのオブジェクトを構築する方法を指示する必要はなく、リフレクションを使用して自動的に解決できます。
シングルトンのバインディング
``````php
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));
});
`
``````php
$this->app->singletonIf(Transistor::class, function (Application $app) {
return new Transistor($app->make(PodcastParser::class));
});
`
スコープ付きシングルトンのバインディング
scoped
メソッドは、特定のLaravelリクエスト/ジョブライフサイクル内で1回だけ解決されるべきクラスまたはインターフェースをコンテナにバインドします。このメソッドはsingleton
メソッドに似ていますが、scoped
メソッドを使用して登録されたインスタンスは、Laravelアプリケーションが新しい「ライフサイクル」を開始するたびにフラッシュされます。たとえば、Laravel Octaneワーカーが新しいリクエストを処理する場合や、Laravelのキューイワーカーが新しいジョブを処理する場合です:
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);
インターフェースを実装にバインドする
サービスコンテナの非常に強力な機能は、インターフェースを特定の実装にバインドする能力です。たとえば、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');
});
コンテキスト属性
コンテキストバインディングは、ドライバーや構成値の実装を注入するために使用されることが多いため、Laravelは、サービスプロバイダーで手動でコンテキストバインディングを定義することなく、これらのタイプの値を注入するためのさまざまなコンテキストバインディング属性を提供します。
たとえば、Storage
属性を使用して特定のストレージディスクを注入できます:
<?php
namespace App\Http\Controllers;
use Illuminate\Container\Attributes\Storage;
use Illuminate\Contracts\Filesystem\Filesystem;
class PhotoController extends Controller
{
public function __construct(
#[Storage('local')] protected Filesystem $filesystem
)
{
// ...
}
}
``````php
<?php
namespace App\Http\Controllers;
use Illuminate\Container\Attributes\Auth;
use Illuminate\Container\Attributes\Cache;
use Illuminate\Container\Attributes\Config;
use Illuminate\Container\Attributes\DB;
use Illuminate\Container\Attributes\Log;
use Illuminate\Container\Attributes\Tag;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Database\Connection;
use Psr\Log\LoggerInterface;
class PhotoController extends Controller
{
public function __construct(
#[Auth('web')] protected Guard $auth,
#[Cache('redis')] protected Repository $cache,
#[Config('app.timezone')] protected string $timezone,
#[DB('mysql')] protected Connection $connection,
#[Log('daily')] protected LoggerInterface $log,
#[Tag('reports')] protected iterable $reports,
)
{
// ...
}
}
`
さらに、Laravelは、現在認証されているユーザーを特定のルートまたはクラスに注入するためのCurrentUser
属性を提供します:
use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;
Route::get('/user', function (#[CurrentUser] User $user) {
return $user;
})->middleware('auth');
カスタム属性の定義
独自のコンテキスト属性を作成するには、Illuminate\Contracts\Container\ContextualAttribute
契約を実装します。コンテナは、属性のresolve
メソッドを呼び出し、そのメソッドは属性を利用するクラスに注入されるべき値を解決する必要があります。以下の例では、Laravelの組み込みConfig
属性を再実装します:
<?php
namespace App\Attributes;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Config implements ContextualAttribute
{
/**
* Create a new attribute instance.
*/
public function __construct(public string $key, public mixed $default = null)
{
}
/**
* Resolve the configuration value.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('config')->get($attribute->key, $attribute->default);
}
}
プリミティブのバインディング
時には、いくつかの注入されたクラスを受け取るクラスがあり、整数などの注入されたプリミティブ値も必要とする場合があります。コンテキストバインディングを使用して、クラスが必要とする任意の値を簡単に注入できます:
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),
];
});
便利なことに、Firewall
がFilter
インスタンスを必要とするたびに、解決されるクラス名の配列を提供することもできます:
$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'));
});
バインディングの拡張
``````php
$this->app->extend(Service::class, function (Service $service, Application $app) {
return new DecoratedService($service);
});
`
解決
makeメソッド
``````php
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]);
``````php
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);
Laravelコンテナインスタンス自体をコンテナによって解決されるクラスに注入したい場合は、クラスのコンストラクタでIlluminate\Container\Container
クラスを型ヒントすることができます:
use Illuminate\Container\Container;
/**
* Create a new class instance.
*/
public function __construct(
protected Container $container,
) {}
自動注入
また、重要なことに、コンテナによって解決されるクラスのコンストラクタで依存関係を型ヒントすることができます。これには、コントローラー、イベントリスナー、ミドルウェアなどが含まれます。さらに、キューイジョブのhandle
メソッドで依存関係を型ヒントすることもできます。実際には、ほとんどのオブジェクトはコンテナによって解決されるべきです。
たとえば、コントローラーのコンストラクタでアプリケーションによって定義されたサービスを型ヒントすることができます。サービスは自動的に解決され、クラスに注入されます:
<?php
namespace App\Http\Controllers;
use App\Services\AppleMusic;
class PodcastController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected AppleMusic $apple,
) {}
/**
* Show information about the given podcast.
*/
public function show(string $id): Podcast
{
return $this->apple->findPodcast($id);
}
}
メソッドの呼び出しと注入
時には、オブジェクトインスタンスのメソッドを呼び出したいが、そのメソッドの依存関係をコンテナが自動的に注入できるようにしたい場合があります。たとえば、次のクラスがあるとします:
<?php
namespace App;
use App\Services\AppleMusic;
class PodcastStats
{
/**
* Generate a new podcast stats report.
*/
public function generate(AppleMusic $apple): array
{
return [
// ...
];
}
}
``````php
use App\PodcastStats;
use Illuminate\Support\Facades\App;
$stats = App::call([new PodcastStats, 'generate']);
`
``````php
use App\Services\AppleMusic;
use Illuminate\Support\Facades\App;
$result = App::call(function (AppleMusic $apple) {
// ...
});
`
コンテナイベント
サービスコンテナは、オブジェクトを解決するたびにイベントを発火します。このイベントを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
のインスタンスがスローされます。