Контролер - це шар HTTP: він приймає запит, дістає з нього дані, викликає бізнес-логіку й формує відповідь. Сервіс - це сама бізнес-логіка, яка про HTTP нічого не знає.
Як виглядає змішування:
public function store(Request $request)
{
$order = Order::create($request->validated());
$order->items()->createMany($request->input('items'));
$total = collect($request->input('items'))->sum(fn ($i) => $i['price'] * $i['qty']);
$order->update(['total' => $total]);
Mail::to($order->email)->send(new OrderPlaced($order));
Http::post('https://warehouse.example/orders', $order->toArray());
return redirect()->route('orders.show', $order);
}
Тут порахунок суми, лист і виклик складу існують лише всередині HTTP-запиту.
Те саме з сервісом:
class PlaceOrder
{
public function handle(array $data): Order
{
return DB::transaction(function () use ($data) {
$order = Order::create($data);
$order->items()->createMany($data['items']);
$order->update(['total' => $this->total($data['items'])]);
OrderPlaced::dispatch($order);
return $order;
});
}
}
public function store(StoreOrderRequest $request, PlaceOrder $placeOrder)
{
$order = $placeOrder->handle($request->validated());
return redirect()->route('orders.show', $order);
}
Що це дає:
- Ту саму логіку можна викликати з Artisan-команди, черги чи імпорту - не тільки з форми.
- Тестувати можна без HTTP-запиту: створили масив, викликали
handle(), перевірили результат. - Транзакція охоплює всю операцію цілком.
- Контролер знову читається за кілька секунд.
Не поспішайте. Для Post::create($request->validated()) окремий сервіс - зайвий шар. Виносити варто тоді, коли з'являється щось із трьох: кілька моделей в одній операції, потреба викликати логіку не з HTTP, або контролер перестав вміщатися на екран.