Beschrijving
Kapsel herbruikbare businessoperaties in met één doel gerichte action-classes met expliciete afhankelijkheden.
Aanbevolen situatie
Gebruik dit wanneer businesslogica omvangrijk is, hergebruikt wordt over meerdere ingangen, of afhankelijk is van externe services.
Menselijke begeleiding
Action-classes zijn aanroepbare classes met één doel die één afzonderlijke businessoperatie inkapselen. In combinatie met constructor dependency injection en het programmeren tegen interfaces op systeemgrenzen houden ze controllers dun, businesslogica testbaar en externe afhankelijkheden uitwisselbaar.
Waarom
- Single responsibility: Elke action-class doet één ding goed, waardoor businesslogica eenvoudig te vinden, te testen en aan te passen is
- Herbruikbaarheid: Dezelfde action kan worden aangeroepen vanuit controllers, commands, jobs en andere actions
- Testbaarheid: Constructor injection maakt afhankelijkheden expliciet en eenvoudig te mocken
- Uitwisselbaarheid: Programmeren tegen interfaces op systeemgrenzen (payment gateways, notificatiekanalen, externe API's) maakt het mogelijk implementaties te vervangen zonder de businesslogica te wijzigen
Geschikt voor
- Businessoperaties die vanuit meerdere plekken worden aangeroepen (controllers, commands, jobs)
- Operaties met externe afhankelijkheden die in tests mockbaar moeten zijn
- Complexe operaties die controllers of jobs te groot zouden maken
- Applicaties met meerdere integratiepunten (payment providers, verzendservices, enz.)
Minder geschikt voor
- Eenvoudige CRUD-operaties die maar op één plek worden gebruikt
- Operaties waarbij één Eloquent-aanroep volstaat
- Prototyping of wegwerpcode waar de overhead niet gerechtvaardigd is
Voorbeelden
Action-class met één doel
class CreateOrderAction
{
public function __construct(private InventoryService $inventory) {}
public function execute(array $data): Order
{
$order = Order::create($data);
$this->inventory->reserve($order);
return $order;
}
}
Gebruik dependency injection
Gebruik altijd constructor injection. Vermijd app() of resolve() binnen classes:
// Slecht: service locator-patroon
class OrderController extends Controller
{
public function store(StoreOrderRequest $request)
{
$service = app(OrderService::class);
return $service->create($request->validated());
}
}
// Goed: constructor injection
class OrderController extends Controller
{
public function __construct(private OrderService $service) {}
public function store(StoreOrderRequest $request)
{
return $this->service->create($request->validated());
}
}
Programmeer tegen interfaces op systeemgrenzen
Steun op contracts voor externe integraties om testbaarheid en uitwisselbaarheid mogelijk te maken:
// Slecht: concrete afhankelijkheid
class OrderService
{
public function __construct(private StripeGateway $gateway) {}
}
// Goed: interface-afhankelijkheid
interface PaymentGateway
{
public function charge(int $amount, string $customerId): PaymentResult;
}
class OrderService
{
public function __construct(private PaymentGateway $gateway) {}
}
Bind in een service provider:
$this->app->bind(PaymentGateway::class, StripeGateway::class);
Meer informatie
- Laravel Service Container Documentation
- Laravel Service Providers Documentation
- Laravel Boost Best Practices PR
Boost guideline
---
title: Use Action Classes for Business Logic
description: Encapsulate reusable business operations in single-purpose action classes with explicit dependencies.
recommended_situation: Use when business logic is substantial, reused across entry points, or depends on external services.
---
- Extract substantial business operations into focused action classes instead of embedding them in controllers, commands, or jobs.
- Give each action one clear responsibility and inject its collaborators through the constructor.
- Reuse actions across entry points when the same operation is triggered from HTTP, CLI, queues, or events.
- Depend on interfaces at external system boundaries so business logic remains testable and implementations stay swappable.