今の現場に入場したばかりのころ、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([...]);
}
デメリット:
-
インスタンス生成コストの無駄
- 重たいクラスや依存関係が多い場合は、パフォーマンスに影響。
Laravelで依存性注入を使わずに毎回インスタンス化するデメリットを検証してみた
- 重たいクラスや依存関係が多い場合は、パフォーマンスに影響。
-
状態の保持ができない
- もしクラス内に設定や状態が必要な場合、毎回リセットされる。
-
テストの柔軟性が低い
-
MockPostService
などを差し込んでテストすることが困難。
-
-
依存が増えると書き換えが大変
-
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を支えているのがサービスコンテナ。インスタンスの生成・管理・共有が行われている
自分の中でもやっと腑に落ちたので、同じような「なんとなくわかってるけど言語化できない」人の参考になれば嬉しいです!