2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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

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

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

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

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

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

Fat Controllerな例

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');
}

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

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

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

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

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

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 の例

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

デメリット:

  1. インスタンス生成コストの無駄

  2. 状態の保持ができない

    • もしクラス内に設定や状態が必要な場合、毎回リセットされる。
  3. テストの柔軟性が低い

    • MockPostService などを差し込んでテストすることが困難。
  4. 依存が増えると書き換えが大変

    • new PostService($a, $b, $c...) が各所にあると、コンストラクタ変更が面倒。

LaravelにおけるDI(依存性注入)の便利さ

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

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

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

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

private $postService;

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

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

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

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

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

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

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

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

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

最後に

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

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

まとめ

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

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?