【Laravel】サービスコンテナとサービスプロバイダの仕組みをわかりやすく解説
はじめに
Laravelを使い続けていると、「サービスコンテナって何をしているのか」「サービスプロバイダはどこに何を書けばいいのか」という疑問に当たることがあります。
ドキュメントを読んでも「依存性注入」「バインディング」「解決」といった用語が並んでいて、最初はイメージしにくいかもしれません。
この記事では、サービスコンテナとサービスプロバイダの役割を具体的なコード例とともに整理します。仕組みを理解すると、コードの設計がシンプルになり、テストも書きやすくなります。
サービスコンテナとは
サービスコンテナは、クラスの生成と依存関係の解決を一元管理する仕組みです。「DIコンテナ」とも呼ばれます。
簡単に言うと「クラスを登録しておくと、必要なときに自動で組み立てて返してくれる箱」です。
なぜサービスコンテナが必要か
サービスコンテナを使わない場合、依存するクラスを自分で new して渡す必要があります。
// サービスコンテナなし:依存関係を手動で組み立てる
$mailer = new SmtpMailer();
$logger = new FileLogger();
$service = new UserService($mailer, $logger);
依存するクラスが増えると組み立てが複雑になり、テスト時にモックに差し替えることも難しくなります。
サービスコンテナを使うと、依存関係の解決をコンテナに任せられます。
// サービスコンテナあり:コンテナが依存関係を自動解決
$service = app(UserService::class);
コンテナが UserService のコンストラクタを見て、必要なクラスを自動的に解決してインスタンスを生成します。
自動解決(オートワイヤリング)
Laravelのサービスコンテナは、タイプヒントを見て依存関係を自動解決します。明示的なバインディングが不要なケースも多いです。
<?php
namespace App\\Services;
class MailService
{
public function send(string $to, string $body): void
{
// メール送信処理
}
}
<?php
namespace App\\Services;
class UserService
{
public function __construct(
private MailService $mailService,
) {}
public function register(string $email): void
{
// ユーザー登録処理
$this->mailService->send($email, 'ようこそ!');
}
}
// コンテナが MailService を自動解決して UserService を生成
$userService = app(UserService::class);
コントローラのコンストラクタでも同様に自動解決が働きます。
<?php
namespace App\\Http\\Controllers;
use App\\Services\\UserService;
class UserController extends Controller
{
public function __construct(
private UserService $userService,
) {}
public function store(): void
{
$this->userService->register('alice@example.com');
}
}
バインディング
インターフェースと実装クラスをコンテナに登録することを「バインディング」と言います。これにより、依存先をインターフェースに向けたまま、実装クラスを差し替えられるようになります。
bind
リクエストごとに新しいインスタンスを生成します。
use App\\Contracts\\MailerInterface;
use App\\Services\\SmtpMailer;
app()->bind(MailerInterface::class, SmtpMailer::class);
// 毎回新しいインスタンスが返る
$mailer1 = app(MailerInterface::class);
$mailer2 = app(MailerInterface::class);
// $mailer1 !== $mailer2
singleton
アプリケーションのライフサイクル中に一度だけインスタンスを生成し、以降は同じインスタンスを返します。DBコネクションやキャッシュドライバなど、状態を共有したいクラスに使います。
app()->singleton(MailerInterface::class, SmtpMailer::class);
// 常に同じインスタンスが返る
$mailer1 = app(MailerInterface::class);
$mailer2 = app(MailerInterface::class);
// $mailer1 === $mailer2
instance
すでに生成済みのインスタンスを登録します。
$mailer = new SmtpMailer();
app()->instance(MailerInterface::class, $mailer);
クロージャでバインディング
生成時に追加の設定が必要な場合はクロージャを使います。
app()->bind(MailerInterface::class, function ($app) {
return new SmtpMailer(
host: config('mail.host'),
port: config('mail.port'),
);
});
コンテナからの取り出し(解決)
バインディングしたクラスを取り出すには app() ヘルパや App::make() を使います。
// 書き方は複数ある(いずれも同じ)
$mailer = app(MailerInterface::class);
$mailer = app()->make(MailerInterface::class);
$mailer = \\App::make(MailerInterface::class);
実務では直接 app() で解決することは少なく、コンストラクタインジェクションで受け取るのが基本です。
サービスプロバイダとは
サービスプロバイダは、サービスコンテナへのバインディングをまとめて定義する場所です。
Laravelのすべての機能(ルーティング・DB・キューなど)はサービスプロバイダを通じて初期化されています。自分のアプリケーションでも、カスタムのバインディングやイベント登録などをサービスプロバイダにまとめて書きます。
config/app.php の providers 配列に登録されたプロバイダが、アプリケーション起動時に順番に実行されます。
サービスプロバイダの構造
<?php
namespace App\\Providers;
use Illuminate\\Support\\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
// バインディングの登録(早期に実行、他のサービスに依存しない処理)
public function register(): void
{
$this->app->singleton(MailerInterface::class, SmtpMailer::class);
}
// イベント登録・ルート登録など(すべてのプロバイダのregisterが終わった後に実行)
public function boot(): void
{
// ビューコンポーザ・イベントリスナーの登録など
}
}
register() と boot() の使い分けが重要です。
| メソッド | 実行タイミング | 用途 |
|---|---|---|
register() | すべてのプロバイダより先 | バインディングの登録のみ |
boot() | すべての register() が完了後 | 他のサービスに依存する処理 |
register() 内で他のサービスを使おうとすると、まだそのサービスが登録されていない場合があるため注意が必要です。
カスタムサービスプロバイダの作成
php artisan make:provider PaymentServiceProvider
app/Providers/PaymentServiceProvider.php が生成されます。
<?php
namespace App\\Providers;
use App\\Contracts\\PaymentGatewayInterface;
use App\\Services\\StripePaymentGateway;
use Illuminate\\Support\\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(PaymentGatewayInterface::class, function ($app) {
return new StripePaymentGateway(
apiKey: config('services.stripe.secret'),
);
});
}
public function boot(): void
{
//
}
}
Laravel 11以降では bootstrap/providers.php に追加します。
// bootstrap/providers.php
return [
App\\Providers\\AppServiceProvider::class,
App\\Providers\\PaymentServiceProvider::class,
];
Laravel 10以前では config/app.php の providers 配列に追加します。
// config/app.php
'providers' => [
// ...
App\\Providers\\PaymentServiceProvider::class,
],
bindings と singletons プロパティ
シンプルなバインディングであれば、register() を書かずにプロパティで定義できます。
class AppServiceProvider extends ServiceProvider
{
// bind と同等
public array $bindings = [
MailerInterface::class => SmtpMailer::class,
];
// singleton と同等
public array $singletons = [
PaymentGatewayInterface::class => StripePaymentGateway::class,
];
}
実務でよく使うパターン
インターフェースと実装を差し替える
外部サービスへの依存をインターフェースで抽象化しておくと、実装を差し替えやすくなります。
// インターフェース
interface NotificationInterface
{
public function send(string $message): void;
}
// Slack実装
class SlackNotification implements NotificationInterface
{
public function send(string $message): void
{
// Slack WebhookへPOST
}
}
// メール実装
class MailNotification implements NotificationInterface
{
public function send(string $message): void
{
// メール送信
}
}
// サービスプロバイダでバインディング
$this->app->singleton(
NotificationInterface::class,
SlackNotification::class
);
テスト時はモックに差し替えるだけで、通知が実際に送信されることなくテストできます。
// テスト
$this->app->instance(
NotificationInterface::class,
Mockery::mock(NotificationInterface::class)
);
preventLazyLoadingをboot()で設定する
N+1問題の自動検知のような「全体に影響する設定」は AppServiceProvider の boot() にまとめます。
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}
macroの登録
boot() でコレクションやリクエストにカスタムメソッドを追加できます。
public function boot(): void
{
Collection::macro('toAssoc', function () {
return $this->reduce(function ($assoc, $item) {
$assoc[$item['key']] = $item['value'];
return $assoc;
}, []);
});
}
まとめ
| 用語 | 役割 |
|---|---|
| サービスコンテナ | クラスの生成と依存関係解決を一元管理する仕組み |
| バインディング | インターフェースと実装クラスをコンテナに登録すること |
bind() | リクエストごとに新しいインスタンスを生成 |
singleton() | 一度だけ生成し、以降は同じインスタンスを返す |
| サービスプロバイダ | バインディングやブート処理をまとめる場所 |
register() | バインディングのみ記述(他のサービスに依存しない) |
boot() | register完了後に実行(イベント・マクロ・設定など) |
サービスコンテナとサービスプロバイダを理解すると、Laravelのコードが「なぜこう動くのか」がわかるようになり、設計の選択肢も広がります。インターフェースと実装を分離してサービスプロバイダでバインディングする設計を意識すると、テストしやすく変更に強いコードが書けます。



