あっぽログ
← 記事一覧に戻る

LaravelのサービスコンテナとDIを理解する:バインディングから依存解決まで

サービスコンテナとは?

Laravelの中核を担う仕組みの一つがサービスコンテナ(IoC コンテナ)です。クラスの依存関係を自動的に解決してくれる「接着剤」のような役割を持ちます。

難しく聞こえますが、要は「このクラスが必要なときはこのやり方で作ってね」という設定を登録しておくと、Laravelが必要なタイミングで自動的にインスタンスを生成してくれる仕組みです。

なぜ依存性注入が必要か

まず、依存性注入(DI)がない場合の問題点を見てみましょう。

class OrderService
{
    public function place(): void
    {
        // 直接インスタンスを生成している
        $mailer = new SmtpMailer();
        $mailer->send('注文を受け付けました');
    }
}

この書き方では SmtpMailer に強く依存しており、テスト時にモックへ差し替えることが難しくなります。

依存性注入を使うと、外から依存を「注入」できます。

class OrderService
{
    public function __construct(
        private MailerInterface $mailer
    ) {}

    public function place(): void
    {
        $this->mailer->send('注文を受け付けました');
    }
}

MailerInterface を実装したクラスなら何でも受け取れるため、テスト時はモックを渡すだけでOKです。

サービスコンテナへの登録(バインディング)

依存の解決ルールを登録することをバインディングと呼びます。通常は App\Providers\AppServiceProviderregister メソッドに書きます。

基本的なバインディング

use App\Mail\SmtpMailer;
use App\Contracts\MailerInterface;

public function register(): void
{
    $this->app->bind(MailerInterface::class, SmtpMailer::class);
}

これで「MailerInterface が必要なら SmtpMailer を作って渡す」という設定が完了です。

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

毎回新しいインスタンスを作るのではなく、アプリケーション全体で同じインスタンスを使い回したい場合は singleton を使います。

$this->app->singleton(MailerInterface::class, SmtpMailer::class);

データベース接続やキャッシュドライバなど、一度だけ初期化すれば十分なクラスに適しています。

クロージャを使ったバインディング

インスタンスの生成時に追加処理が必要な場合は、クロージャで柔軟に定義できます。

$this->app->bind(MailerInterface::class, function ($app) {
    return new SmtpMailer(
        host: config('mail.host'),
        port: config('mail.port'),
    );
});

サービスコンテナからの解決

登録したバインディングは、いくつかの方法で取り出せます。

コンストラクタインジェクション(推奨)

コントローラやサービスのコンストラクタに型宣言するだけで、Laravelが自動的に解決してくれます。

class OrderController extends Controller
{
    public function __construct(
        private OrderService $orderService
    ) {}

    public function store(Request $request): JsonResponse
    {
        $this->orderService->place();
        return response()->json(['message' => '注文完了']);
    }
}

メソッドインジェクション

コントローラのアクションメソッドでも型宣言による注入が使えます。

public function store(Request $request, OrderService $orderService): JsonResponse
{
    $orderService->place();
    return response()->json(['message' => '注文完了']);
}

app() ヘルパーを使う

手動でコンテナから取り出すこともできます。

$mailer = app(MailerInterface::class);

ただしこの書き方はテストしにくくなるため、可能な限りコンストラクタインジェクションを使うのがベストプラクティスです。

ファサードとの関係

Laravelの Mail::send() のような静的メソッド風に書けるのがファサードです。内部ではサービスコンテナが解決したインスタンスに処理を委譲しています。

// ファサードを使う場合
Mail::to('user@example.com')->send(new OrderMail());

// コンテナ経由と実質同じ
app('mailer')->to('user@example.com')->send(new OrderMail());

ファサードは手軽に使えますが、依存が暗黙的になるため大規模プロジェクトではDIを明示するほうが管理しやすいケースもあります。

まとめ

概念説明
bind呼ぶたびに新しいインスタンスを生成
singletonアプリ全体で同一インスタンスを共有
コンストラクタDI型宣言だけで自動注入(推奨)
app() ヘルパー手動でコンテナから取り出す

サービスコンテナを活用することで、クラスの依存関係が明示的になり、テストやリファクタリングがしやすいコードを書けるようになります。まずはサービスプロバイダに bindsingleton を書くことから試してみてください。

← 記事一覧に戻る