Товстий контролер - метод контролера на сотню рядків, де змішано все: валідацію, перевірку прав, бізнес-правила, роботу з базою, відправку листів, виклики сторонніх API, формування відповіді.
public function store(Request $request)
{
$request->validate([...]); // валідація
if ($request->user()->orders()->count() > 10) {} // бізнес-правило
$order = Order::create([...]); // збереження
foreach ($request->items as $item) { /* ... */ } // розрахунки
Mail::to($request->user())->send(new OrderPlaced($order));
Http::post('https://crm.example/api/...', [...]); // інтеграція
return response()->json(...);
}
Чому це погано: логіку неможливо перевикористати (з команди Artisan, черги, API й адмінки потрібна та сама дія), важко тестувати окремо від HTTP, кожна зміна чіпає великий метод.
Що куди переносити:
| Що | Куди |
|---|---|
| валідація й авторизація запиту | Form Request (rules(), authorize()) |
| права на конкретні об'єкти | політики ($this->authorize('update', $order)) |
| бізнес-операція | action-клас чи сервіс (PlaceOrder) |
| реакції на подію (листи, інтеграції) | події й слухачі, часто в черзі |
| довгі чи ненадійні операції | джоби в черзі |
| форматування відповіді | API Resource, view |
| запити, що повторюються | scopes моделі, query-класи |
Після рефакторингу контролер - тонкий координатор:
public function store(StoreOrderRequest $request, PlaceOrder $placeOrder)
{
$order = $placeOrder->handle($request->user(), $request->validated());
return OrderResource::make($order)->response()->setStatusCode(201);
}
Він знає лише про HTTP: отримати перевірений запит, викликати дію, повернути відповідь.
Не варто впадати в іншу крайність: для простого CRUD без бізнес-логіки Post::create($request->validated()) у контролері - цілком нормально. Окремий клас на кожен рядок коду - теж складність. Виносити варто, коли з'являється реальна логіка або потреба викликати ту саму дію з кількох місць.