Action-клас - клас, що виконує одну бізнес-операцію і має один публічний метод (handle() чи __invoke()):
final class PlaceOrder
{
public function __construct(
private InventoryService $inventory,
private PaymentGateway $payments,
) {}
public function handle(User $user, array $data): Order
{
return DB::transaction(function () use ($user, $data) {
$order = $user->orders()->create([...]);
$this->inventory->reserve($order);
$this->payments->authorize($order);
OrderPlaced::dispatch($order);
return $order;
});
}
}
Сервіс - клас, що групує кілька операцій навколо однієї області: OrderService з place(), cancel(), refund(), ship().
Переваги action-класів:
- назва = бізнес-операція:
PlaceOrder,CancelSubscription,InviteTeamMember- структура папкиapp/Actionsчитається як перелік того, що вміє застосунок; - маленькі й сфокусовані - не розростаються до «сервісу на 2000 рядків», який з часом обростає всім, що стосується замовлень;
- точні залежності: кожен action впроваджує лише те, що йому потрібно;
- виклик звідусіль: контролер, команда Artisan, джоба, Livewire-компонент, тест - та сама дія;
- легко тестувати: одна операція - один набір тестів.
Коли сервіс доречніший:
- спільний стан чи конфігурація для групи пов'язаних операцій (клієнт стороннього API з методами для різних ендпойнтів);
- обгортка над інфраструктурою:
ExchangeRateService,GeoIpService- не бізнес-операції, а технічні можливості; - кілька дрібних операцій, які окремими класами створили б шум.
Практики, що добре працюють разом:
- action приймає перевірені дані (масив з
validated()чи DTO), а неRequest- щоб працювати поза HTTP; - транзакція - межа action-а;
- побічні реакції - через події, а не прямими викликами в action;
- action може викликати інші actions, але глибокі ланцюжки - сигнал, що межі обрано невдало.
Цей підхід використовують офіційні стартові набори й Fortify (app/Actions/Fortify/CreateNewUser). Пакет lorisleiva/laravel-actions дає змогу одному класу бути і контролером, і джобою, і командою - зручно, але додає «магії».