laravel-actions

Installation
SKILL.md

Laravel Actions

An action is one domain operation in one class with one public method, __invoke(). All business logic lives in actions. Everything that triggers an operation — a controller, job, listener, command or the scheduler — gathers its input and calls one action, nothing more.

Rules

  • One operation per class. Named {Verb}{Subject}Action (CancelOrderAction, GetInvoiceTotalsAction), under app/Actions/{Entity or Subsystem}/. Never group by verb.
  • Typed input. Take the models the operation acts on, plus a DTO, enum or value object for everything else. Never an array of request data or a loose run of primitives.
  • The acting user is an argument. When the operation depends on who's doing it, the caller passes the user in. Never call user() or auth() inside an action: it would then only work inside a request.
  • Return what the caller needs. The model it changed, the thing it created, or void. Return the model you hold; don't re-query it with fresh().
  • Guard first. Business-rule preconditions go in a private guard() called before any work, using throw_unless/throw_if with a domain exception factory. A state precondition asks the state: $order->state->canTransitionTo(OrderCancelled::class), or a model is…() helper. On an event-sourced model the aggregate guards instead, and the action doesn't (laravel-event-sourcing).
  • Transaction when there's more than one write. Wrap in DB::transaction when the action makes several writes, or a write plus something that must roll back with it. A single write needs none.
  • Nothing external inside the transaction. HTTP calls, file storage and payments run before or after the closure, never inside it; a rollback can't undo them.
  • Reads can be actions too. A read that computes, caches, transforms or calls a service is a Get…Action or Fetch…Action. A filterable, paginated listing is a query class instead (see laravel-controllers).
  • Context in mutable private properties. When several private methods need the same input, store it in private Order $order;, never readonly: the same instance is invoked again and again, for example once per item in a loop.
  • resolve(), never app().

Example

Installs
57
GitHub Stars
15
First Seen
Mar 27, 2026