Laravel開発、new を使ってます？DI・サービスクラス・コンテナの話

今の現場に入場したばかりのころ、Laravelのプロジェクトで、サービスクラスをメソッド内で `new` して使っているコードに出会いました。

自分は「**コンストラクタで依存性注入（DI）したほうがよくないか？？**」と思っていたので、何も考えずに DI にリファクタしていたんですが…

ふと、「なんとなくDIの方がいいって思ってるけど、ちゃんと理由を説明できるかな？」と考えてみたら、サービスクラスとサービスコンテナって別物？？など意外とあやふや。

この機会に、**Fat Controller問題 → サービスクラス → DI → サービスコンテナ**という流れを自分なりに整理してみました。


## サービスクラスを作る理由 → Fat Controllerを避けたい

Laravelのコントローラーに処理をどんどん追加していくと、バリデーション、ビジネスロジック、データ操作などが混在してメンテ不能な「**Fat Controller**」になります。

### Fat Controllerな例
```php
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|string|max:255'
    ]);

    $post = new Post();
    $post->title = $validated['title'];
    $post->save();

    return redirect()->route('post.index');
}
```

このままでも動くけど、**コードの再利用性・可読性・テスト性はかなり低い**です。

### そこでサービスクラスを作る

ビジネスロジック部分を切り出して、コントローラーを「流れの制御」に専念させます。

```php
class PostService
{
    public function create(array $data)
    {
        return Post::create($data);
    }
}
```

コントローラー側はこうなってスッキリ

```php
public function store(Request $request, PostService $postService)
{
    $validated = $request->validate([
        'title' => 'required|string|max:255'
    ]);

    $postService->create($validated);
    return redirect()->route('post.index');
}
```

## `new` で毎回インスタンスを生成することのデメリット

「じゃあ PostService を `new` すればいいのでは？」という気持ち、わかります。

でも `new` を多用するのは、**以下のような問題があるため推奨されていません**。

### ❌ 毎回 `new` の例

```php
public function store(Request $request)
{
    $service = new PostService();
    $service->create([...]);
}
```

### デメリット：

1. **インスタンス生成コストの無駄**
    - 重たいクラスや依存関係が多い場合は、パフォーマンスに影響。
[Laravelで依存性注入を使わずに毎回インスタンス化するデメリットを検証してみた](https://qiita.com/t-mandokoro/items/f2237ef4a6a52f535471)

2. **状態の保持ができない**
    - もしクラス内に設定や状態が必要な場合、毎回リセットされる。
3. **テストの柔軟性が低い**
    - `MockPostService` などを差し込んでテストすることが困難。
4. **依存が増えると書き換えが大変**
    - `new PostService($a, $b, $c...)` が各所にあると、コンストラクタ変更が面倒。



## LaravelにおけるDI（依存性注入）の便利さ

Laravelでは、クラスをメソッドの引数や `__construct()` に書くだけで、**サービスコンテナが自動でインスタンスを解決して注入してくれます。**

### メソッドインジェクション（単発で使う時に便利）

```php
public function store(PostService $postService)
{
    $postService->create([...]);
}
```

### コンストラクタインジェクション（クラス全体で使うときに便利）

```php
private $postService;

public function __construct(PostService $postService)
{
    $this->postService = $postService;
}

public function store(Request $request)
{
    $this->postService->create([...]);
}
```

どちらを使っても OK！使いどころで選べます。


## DI を支えているのが「サービスコンテナ」

Laravelの**サービスコンテナ**は、依存するクラスのインスタンスを自動的に解決・注入してくれる仕組みです。

さらに、サービスを **シングルトンとして登録**することもできて、**インスタンスを1度だけ生成して再利用**することも可能です。

```php
App::singleton(PostService::class, fn($app) => new PostService());
```

内部的には、サービスコンテナがインスタンスを**キャッシュ（配列）で保持**して使いまわしているそうです。
[【Laravel】サービスコンテナ・サービスプロバイダ・依存性注入の仕組みを整理した](https://zenn.dev/104/articles/2aa14172ddaa86)

この仕組みを使えば、コントローラーやサービスクラス間で同じインスタンスを共有できたり、重たい処理の最適化にもつながります。



## 最後に

混ざりがちな「サービスクラス」「DI」「サービスコンテナ」の関係はこう理解するとスッキリするかな？って思いました。

```
 Fat Controller 対策
　↓ 
 サービスクラスを作る
　↓
 どう使う？
　↓
 new せず DI（依存性注入）で使う
　↓
 それを支えてるのがサービスコンテナ
```


## まとめ

- Laravelでは「Fat Controller」を防ぐためにサービスクラスでロジックを分離する
- サービスクラスは `new` ではなく、LaravelのDIで注入する方が拡張性・テスト性に優れる
- DIを支えているのがサービスコンテナ。インスタンスの生成・管理・共有が行われている

自分の中でもやっと腑に落ちたので、同じような「なんとなくわかってるけど言語化できない」人の参考になれば嬉しいです！