Laravelを書いていて、「なんでこれ勝手にインスタンス入ってるの?」って思ったこと、ありませんか?
それ、DI(依存性注入)っていう仕組みのおかげなんですが……正直、仕組みわかってなくてもLaravelって動いちゃうんですよね。
本記事では、「バインドって何?」「インターフェイスって必要なの?」「app()っていつ使うの?」みたいな疑問を、ちょっとゆるめのノリで解きほぐしていきます。
テストが書きやすくなる、保守がしやすくなる、なんとなく設計がスマートに見える(大事)——そんなDIの恩恵を、今よりちょっとだけ実感できるようになる記事を目指しました。
- 1. はじめに
- 1.1. DIって、結局「newしないやつ」ってことでしょ?
- 1.2. 🤖 DIじゃない場合(自力でnew)
- 1.3. 🎁 DIしてる場合(Laravelに任せる)
- 2. なんでDIが大事なの?
- 3. LaravelにおけるDIについての詳細解説
- 3.1. サービスコンテナの概要
- 3.1.1. たとえば、こんなコード:
- 3.1.2. Laravelのサービスコンテナはこんな仕組み:
- 3.2. 🧰 インターフェイスを使うときはバインドが必要
- 3.2.1. たとえば、インターフェースをDIしようとした場合:
- 3.3. 🔧 だから必要なのが「バインド」
- 3.3.1. 💡 バインドはどこに書くの?
- 3.4. bind()とsingleton()の違い:インスタンスっていつ作るのが正解?
- 3.5. 🙄 ほーん、で?それ意味あるんすか?ってあなたへ
- 3.6. ✅ bindのいいところ:
- 3.7. 🧨 singletonの危ないところ:
- 3.8. 💬 結論(読者用まとめ):
- 4. app() / resolve() / make():Laravelで自分でDIするとき
- 4.1. 特徴まとめ
- 5. Laravelは助けてくれる。でも、あなたの設計までは助けてくれない
- 5.1. 🤔 app()で取ってるだけじゃん問題
- 5.2. 🏷 サービスロケータとは何か?
- 5.3. ⚠️ サービスロケータの何がまずいのか?
- 5.4. 🧠 本来、サービスロケータは「密結合を避けるための設計支援」だった
- 5.5. 🧨 instanceofが出てきたら、設計の黄信号
- 5.6. 🤯 本来、interfaceがあるってことは…
- 5.7. ✅ make()は便利だけど、「どういう場面で使うか」を意識しよう
- 5.7.1. 👌 OKパターン:
- 5.7.2. ⚠️ NG寄りパターン:
- 6. 俺がDIの成功例だと思ってるやつ
- 6.1. ✨ 成功例(だとおもうやつ):UseCaseに抽象サービスを注入して動かす構成
- 7. 💥 ここで感じるDIの快感ポイント:
- 8. おわりに
はじめに
DIって、結局「newしないやつ」ってことでしょ?
Laravelを触っていて、「なんかインスタンス勝手に入ってきてるな……」と思ったことありませんか?
それ、DI(依存性注入)という仕組みのおかげです。けど、名前が仰々しいせいで、
「なんか高度な設計の話?」って構えちゃう人、多いと思います(筆者含む)。
でもDIって、ほんとはめちゃくちゃ地味なやつなんです。
ざっくり言うとこういうこと:
🤖 DIじゃない場合(自力でnew)
$userService = new UserService();
🎁 DIしてる場合(Laravelに任せる)
public function __construct(UserService $userService)
Laravelが勝手にUserService
のインスタンスを用意して渡してくれます。
「自分でインスタンス作らない代わりに、外からもらう」。これがDIの中核です。
なんでDIが大事なの?
Laravelでは、DIを使うことでいろんなメリットがあります:
- テストがしやすくなる
→ テスト用のモックやスタブを差し替えるのが簡単になる - 保守がラクになる
→ 実装クラスを変更しても、呼び出し元のコードをいじらずに済む - クラスの責務が明確になる
→ 「このクラスはこれが必要」と明示できるようになる
DIは、「Laravelっぽいコード」の基礎にある考え方。
でも言葉で説明されるとよくわからない部分もあるので、この記事では、実際のコード例やよくある使い方を交えて、
「DIってそういうことか!」と思えるようなガイドを目指していきます。
LaravelにおけるDIについての詳細解説
サービスコンテナの概要
Laravelには、「サービスコンテナ(Service Container)」という、超便利なおせっかい屋さんがいます。
こいつは、必要なクラスを勝手に作って渡してくれるやつです。
たとえば、こんなコード:
class UserController extends Controller
{
protected $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function index()
{
return $this->userService->getAllUsers();
}
}
このコード、UserController
はUserService
を自分でnewしてないですよね?
でもLaravelは動く。なぜかって?サービスコンテナが勝手に用意してくれるから。
Laravelのサービスコンテナはこんな仕組み:
- 「UserServiceが必要なんだな」と型ヒントから察知
- もしバインドされていれば、それに従ってインスタンス化
- 何も指定されてなくても、クラスに引数がなければ勝手にnewする
// 自動的に解決される例(引数なし)
class NotificationService
{
public function send($message)
{
// メッセージを送る処理
}
}
// これを注入
class AlertController extends Controller
{
public function __construct(NotificationService $service)
{
$service->send("Hello!");
}
}
この場合、NotificationService
に特別な設定がなくても、Laravelが自動でnewしてくれます。
「あ、引数ないクラスね?任せて!」って感じ。
🧰 インターフェイスを使うときはバインドが必要
Laravelは、具体的なクラス名が書いてあれば勝手にインスタンスを作ってくれます。
でも、「このクラス、どの実装を使えばいいの?」ってのが明示されてないと、迷子になります。
たとえば、インターフェースをDIしようとした場合:
class UserService
{
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
ここでUserRepository
はインターフェースですが、Laravelくんは何を渡せばいいか分かりません。
interface UserRepository
{
public function getAll();
}
class EloquentUserRepository implements UserRepository
{
public function getAll()
{
return User::all();
}
}
インターフェースはあくまで「こういう操作ができるよ」というルール。
そのままではインスタンス化できないので、Laravelはこう思います:
🐣「UserRepositoryね……え?それどのクラスをnewすればいいの???」
🐤「インターフェースはnewできないんですけど〜〜〜???」
🔧 だから必要なのが「バインド」
// AppServiceProvider.php
public function register()
{
$this->app->bind(
UserRepository::class,
EloquentUserRepository::class
);
}
これでLaravelのサービスコンテナは、「UserRepositoryって言われたらEloquentUserRepositoryを使えばいいんだな」と理解できます。
以降は、どこでUserRepositoryを型ヒントしても、自動で正しい実装が注入されるようになります。
💡 バインドはどこに書くの?
Laravelプロジェクトには、最初から App\Providers\AppServiceProvider
というクラスが用意されています。
app/Providers/AppServiceProvider.php
を開いて、register()
メソッドの中にバインドのコードを書きましょう。
public function register()
{
$this->app->bind(
UserRepository::class,
EloquentUserRepository::class
);
}
Laravelでは、このAppServiceProvider
がサービスの登録場所として用意されている公式な入り口です。
ファイル名に「Service」とか「Provider」ってついてて不安になるかもしれませんが、ここは触ってOKな場所です。
「え、これ自分で書いていいの?」って思ってた人、正解です。書いていいやつです。
バインドの数が増えてきたら、専用のServiceProviderを作ってもOKです。
php artisan make:provider RepositoryServiceProvider
とかして分けてもいい。
(でも最初はAppServiceProviderでOK)
bind()とsingleton()の違い:インスタンスっていつ作るのが正解?
ここまでで、Laravelがサービスコンテナを通じてクラスを注入してくれる仕組みがわかってきました。
でも実は、「サービスコンテナにどうやって登録するか」でも挙動が変わります。
たとえば、同じクラスを使っていても:
- 毎回新しいインスタンスがほしい場合
- 一度だけ作ったインスタンスを使いまわしたい場合
この2つのどっちかで、Laravel側の動きが変わります。
これを制御するのが、bind()
と singleton()
です。
まず、bind
とsingleton
でAppServiceProviderに登録:
// AppServiceProvider.php
public function register()
{
$this->app->bind(SomeClass::class, function ($app) {
return new SomeClass();
});
$this->app->singleton(AppConfigManager::class, function ($app) {
return new AppConfigManager(); // 最初の1回だけ
});
}
bindで登録したサービスコンテナを呼び出す:
$one = app(RandomNumberGenerator::class);
$two = app(RandomNumberGenerator::class);
$one === $two; // false
🤖 別のインスタンスとしてインスタンス化される。
singletonで登録したサービスコンテナを呼び出す:
$a = app(AppConfigManager::class);
$b = app(AppConfigManager::class);
$a === $b; // true
🤖 同じインスタンスとしてインスタンス化される。
🙄 ほーん、で?それ意味あるんすか?ってあなたへ
✅ bindのいいところ:
- 毎回newされる=副作用がない
- テスト中でもインスタンスが汚れない
- 「このインスタンス、他で誰か使ってないよね?」って不安がない
🧨 singletonの危ないところ:
- サービスが状態を持ってたら、全呼び出しで影響を受ける
- 気づかないうちに共有して、バグになってることがある
- テストで「なんか前の値残ってる」系の地獄が始まる
💬 結論(読者用まとめ):
設計に自信がないなら、bindしておけば安全。
singletonは便利だけど、「このインスタンス、絶対に変な状態にならない」と言い切れるときだけ使うのが吉。
singletonってなに?
class MemoPad { public array $notes = []; public function write(string $note) { $this->notes[] = $note; } } // AppServiceProvider.php に↓を登録 $this->app->singleton(MemoPad::class, fn() => new MemoPad()); $pad = app(MemoPad::class); $pad->write("やること:洗濯"); $another = app(MemoPad::class); $another->write("やること:Laravel"); print_r($another->notes); // ["やること:洗濯", "やること:Laravel"]
こんな感じで、新しくインスタンス化したのに、前の状態が残ってるってこと。記憶力に自身がない人は素直にBind使おうね。おじさんとの約束だぞ⭐️

設定情報とか、アプリ全体で共有したい値を持ってるだけのClassとかだったら、singleton使うのがいいのかもね〜
app()
/ resolve()
/ make()
:Laravelで自分でDIするとき
ここまで読んで、「Laravelが勝手に注入してくれるって便利だな〜」と思ってるあなたへ。
それ実際には:
app()
は Laravelのグローバルヘルパ関数で、resolve()
はそのラッパー的存在で、app()->make()
はコンテナのメソッドを直接呼んでる
みたいな関係性があるんですが、
「インスタンスを取得する」っていう目的では、基本的にどれでも同じように使えます。
ただし:「引数付きでインスタンス作りたい」ってときは
make()
が一番素直に書ける、ってことだけ覚えといてくれると◎です。
特徴まとめ
メソッド | 実態 | 特徴 |
---|---|---|
app() | ヘルパ関数 | 書きやすくて一番使われる。resolveのラッパー。 |
resolve() | Laravel独自関数 | DIの雰囲気がある。app()とほぼ同じ挙動。 |
app()->make() | Applicationのメソッド | 引数付きインスタンス生成が一番自然に書ける。 |
app()->make()の利用例: こんなふうに 「コンストラクタに値を渡しつつDIしたい」ってとき、makeは便利。
class SomeClass { public string $name; public function __construct(string $name) { $this->name = $name; } } // AppServiceProvider.phpに登録しなくても使える $someClass = app()->make(SomeClass::class, ['name' => 'たねまる']); echo $someClass->name; // 結果: たねまる
Laravelは助けてくれる。でも、あなたの設計までは助けてくれない
── サービスロケータ vs 依存性注入の微妙なライン
LaravelのDIって、便利すぎる。
引数に型書けばインスタンスが注入されるし、app()
やresolve()
でいつでも取り出せる。
でも、ここでちょっと立ち止まって考えたい。
これ、本当に「DI」なんだっけ?
🤔 app()
で取ってるだけじゃん問題
class SomeController extends Controller
{
public function handle()
{
$userService = app(UserService::class);
$userService->doSomething();
}
}
↑これ、DI“っぽい”けど、よく見るとただの手動で依存を取りに行ってるだけ。
依存性注入じゃなくて、依存性自己調達。
これは「サービスロケータ」と呼ばれるパターンです。
🏷 サービスロケータとは何か?
必要なクラスを、自分でサービスコンテナから引っ張ってくる設計。
DIは「クラスに必要なものを外から渡す」設計。
でもサービスロケータは「必要なものを中で取りに行く」設計。
どっちも動くし、Laravelではどっちもできちゃう。
でも、設計上は大きな違いがある。
⚠️ サービスロケータの何がまずいのか?
- 依存がコードに見えない
- テストしづらくなる(中で勝手に
app()
されたらモックできない) - 読み手が「このクラスが何に依存してるのか」を一発で把握できない
結果:コードが「動くけど読みにくい」ものになっていく。
🧠 本来、サービスロケータは「密結合を避けるための設計支援」だった
本来、DDDやクリーンアーキテクチャでは、
サービスロケータは「newしなくてもインターフェースで解決できるようにしよう」っていう善意で登場したやつ。
でも、Laravelではこれが簡単にどこでも使える便利関数になってしまって、
「使いすぎると、結局newと変わらん設計」になっちゃう。
🧨 instanceofが出てきたら、設計の黄信号
$payment = app(PaymentProviderInterface::class);
if ($payment instanceof StripeProvider) {
$payment->chargeWithExtraLogging($invoice);
} elseif ($payment instanceof PaypalProvider) {
$payment->chargeWithSandboxMode($invoice);
}
🤯 本来、interfaceがあるってことは…
- 呼び出し元は「このメソッドさえ使えれば中身は何でもいい」っていう状態になるべき
- でも、
instanceof
が出てくるってことは、「中身によって使い方変えてる」ってこと - → つまり、それもうinterfaceで隠せてない状態💀
✅ make()は便利だけど、「どういう場面で使うか」を意識しよう
👌 OKパターン:
- ServiceProviderの中で明示的にインスタンス作りたいとき
- ファクトリ的に「使う実装を動的に切り替える必要があるとき」
- クロージャや匿名関数の中で、あえて呼び出したいとき
⚠️ NG寄りパターン:
- 普通のサービスクラスやコントローラーの中で、毎回
app()
やmake()
を直接書いてる - どの依存が必要かがコードを読まないと分からない状態になってる

スコープが閉じてれば使ってもいいってことだよね〜。
俺がDIの成功例だと思ってるやつ
ここまで偉そうに語ってきましたが、DIよく知らない時は単にコーディングルールをなぞってるだけだった。
なんかよくわからんけど、エラーになるからServiceProviderに登録して、UseCaseでDIして、まぁいいやろみたいな感じ。
ただ、UseCase内でビジネスロジックをアプリケーションサービスとして抽象化してみたときにいいないいなDIっていいなって思ったんだ。
✨ 成功例(だとおもうやつ):UseCaseに抽象サービスを注入して動かす構成
// サービスの抽象
interface NotificationServiceInterface
{
public function sendNotification(string $message): void;
}
// サービス本体
class SlackNotificationService implements NotificationServiceInterface
{
public function sendNotification(string $message): void
{
// Slackに通知を送る処理
echo "Slack: {$message}";
}
}
// サービスコンテナ登録
// AppServiceProvider.php
public function register()
{
$this->app->bind(
NotificationServiceInterface::class,
SlackNotificationService::class
);
}
// ユースケース
class NotifyUserUseCase
{
public function __construct(
private NotificationServiceInterface $notificationService
) {}
public function execute(string $message): void
{
$this->notificationService->sendNotification($message); // ← この一行で、責務の分離・DIの意味・設計の綺麗さが全部わかる。激エモ。
}
}
// Controller
class UserController extends Controller
{
public function __construct(private NotifyUserUseCase $useCase) {}
public function notify()
{
$this->useCase->execute('ようこそ、Laravelワールドへ!'); //sampleなのでゆるしてくだちい。
}
}
💥 ここで感じるDIの快感ポイント:
NotifyUserUseCase
がどこから何が来てるか一切気にしてないのに使える- 呼び出し側も 依存を気にせず引数に書くだけ
- 実装を変えたくなったら、ServiceProviderのバインドだけ変えればOK
おわりに
自分の技術知見をちょっと棚卸ししてみるか、くらいの気持ちで書き始めたら、
気づいたらとんでもない文字数になってました。DI、恐るべし。
この記事を書いた理由は、実際に自分がSaaSっぽい何かを作らなきゃいけなくなって、
「どうせやるなら設計ちゃんとやりたいな…」って思ってLaravelの記事を読み漁ってたときの記憶があるからです。
その頃、DIについてまともに解説されてる記事が見つけられなくて、
半端に読んだせいでまさにこの記事で紹介したようなサービスロケータがごろごろ転がってました。
テストについてまだこのブログで書いてないんですが、Mock刺せないんで意味のあるテストがしづらかったです。
もし今この記事を読んでくれてる人が、
当時の自分みたいに「DIってなんなん…」「どう使えばいいの…」ってなってるなら、
少しでもその迷いを減らせたら嬉しいです。
抽象化とかの説明を全く入れてないのが心残りですが、まぁそれは別の機会に記事にしようと思います。
質問とかコメントあればお気軽に書き込んでください。それでは良いDIライフを。
コメントを残す