Одне з ключових архітектурних рішень. Контролери й моделі швидко «розпухають», тож логіку виносять в окремі класи.
Проблема «товстих» контролерів: контролер має лише приймати запит, делегувати роботу й повертати відповідь. Бізнес-логіка в ньому не тестується ізольовано й не перевикористовується.
Service-класи - групують пов'язану логіку домену:
class OrderService
{
public function __construct(
private PaymentGateway $gateway,
private InventoryManager $inventory,
) {}
public function place(User $user, Cart $cart): Order
{
return DB::transaction(function () use ($user, $cart) {
$this->inventory->reserve($cart->items);
$order = $user->orders()->create([/* ... */]);
$this->gateway->charge($user, $cart->total);
OrderPlaced::dispatch($order);
return $order;
});
}
}
Action-класи (single-action) - один клас = одна операція. Дрібніша гранулярність, дуже тестовано:
class PlaceOrderAction
{
public function handle(User $user, Cart $cart): Order { /* ... */ }
}
«Товсті» моделі - логіку, тісно пов'язану з даними самої моделі (скоупи, аксесори, прості методи стану), доречно лишати в моделі. Складні міждоменні операції - у сервіси.
Рекомендації:
- Контролер тонкий: запит → виклик сервісу/екшену → відповідь.
- Складні операції з кількома моделями - у Service/Action із транзакцією.
- Логіка одного агрегату - у моделі (скоупи, обчислювані атрибути).
- Не плодіть абстракцій передчасно - починайте простіше, виносьте за потреби.