🧩
LaravelでControllerを“1Action単位”に分離する理由
はじめに
Laravelは習得しやすく、開発スピードも速いフレームワークです。しかし、機能追加やチーム開発が進むにつれて、以下のような課題に直面したことはありませんか?
- Controllerが肥大化し、何をしているのか分かりにくくなる
- バリデーション、保存処理、ログ出力などが1つのメソッドに詰め込まれる
- 誰がどの責務を持っているのか分かりづらくなる
こうした状態が続くと、メンテナンス性や保守コストが急激に悪化します。
そこで本記事では、「Controllerは1アクション単位で分離する」
という構造化アプローチを紹介します。
実務で蓄積してきたパターンをベースに、「なぜ分離するのか?」「どう分離すればいいのか?」を、テンプレート設計とあわせて解説します。
従来の書き方の問題点
Laravelを使い始めた頃や、小規模なプロジェクトでは以下のようなControllerの書き方がよく見られます。
public function update(Request $request, int $id)
{
$request->validate([
'title' => 'required|string',
'description' => 'nullable|string',
]);
$task = Task::findOrFail($id);
$task->title = $request->input('title');
$task->description = $request->input('description');
$task->save();
Log::info('Task updated', ['task_id' => $task->id]);
return redirect()->route('tasks.index')->with('success', '更新しました');
}
問題点
- 1つのメソッドに複数の責務(バリデーション/保存/ログ/レスポンス)
- テストしづらい構造
- 責務が増えるにつれ肥大化
なぜ分離するのか?
Controllerを分離することで得られる最大のメリットは、「責務の明確化」と「変更に強い構造」
です。
1. Controllerはプレゼンテーション層に徹する
public function __invoke(UpdateRequest $request, int $id): RedirectResponse
{
$dto = new TaskUpdateDto($request);
$this->updateService->handle($dto, $id);
return redirect()->route('tasks.index')->with('success', '更新しました');
}
2. 各層が単一責務になる
+---------------------------+
| Controller |
+---------------------------+
↓
+---------------------------+
| Service |
+---------------------------+
↓
+---------------------------+
| DTO (Data Transfer Object)|
+---------------------------+
↓
+---------------------------+
| Model / Repository |
+---------------------------+
3. テストと保守がしやすくなる
構造化テンプレートの設計戦略
Controller層の方針:1 Controller = 1 Action
public function __invoke(UpdateRequest $request, int $id): RedirectResponse
{
$dto = new TaskUpdateDto($request);
$this->updateService->handle($dto, $id);
return redirect()->route('tasks.index');
}
フォルダ構成例
app/
├── Http/Controllers/Admin/Ongoing/UpdateController.php
├── Services/Status/Ongoing/UpdateService.php
├── DataTransferObjects/Status/Ongoing/Update/TaskUpdateDto.php
実際の構成イメージ
処理フロー
[ Routing ]
↓
[ Controller ]
↓
[ Service ]
↓
[ DTO ]
↓
[ Model / Repository ]
コード構成例
Controller
public function __invoke(UpdateRequest $request, int $id)
{
$dto = new TaskUpdateDto($request);
$this->updateService->handle($dto, $id);
}
DTO
class TaskUpdateDto
{
public function __construct(UpdateRequest $request)
{
$this->title = $request->input('title');
}
public function getTitle(): string
{
return $this->title;
}
}
Service
public function handle(TaskUpdateDto $dto, int $id): void
{
$task = Task::findOrFail($id);
$task->title = $dto->getTitle();
$task->save();
}
まとめ
- Controllerを1Actionごとに分離することで、見通し・保守性が大幅に向上する
- DTO・Service・Modelを適切に役割分担することで再利用性とテスト性も高くなる
- テンプレートではこの分離をルールとして標準化
次回予告
次回は、「DTOはなぜ必要なのか?」をテーマに、DTOの導入メリットや設計ポイントを詳しく解説します!
🚀 テンプレート紹介
本記事の構成は、無料で公開している「Laravel構造化テンプレート」にそのまま導入されています。
業務アプリの設計に悩んでいる方は、ぜひ参考にしてみてください!
🙌 フォロー・スキも励みになります!
「いいね」や「フォロー」をいただけると励みになります!今後もLaravel構造化・実務ノウハウを発信していきます。
Discussion