Loading...
Loading...
Action-oriented architecture for Laravel. Invokable classes that contain domain logic. Use when working with business logic, domain operations, or when user mentions actions, invokable classes, or needs to organize domain logic outside controllers.
npx skill4agent add leeovery/claude-laravel laravel-actions__invoke()<?php
declare(strict_types=1);
namespace App\Actions\Order;
use App\Data\CreateOrderData;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class CreateOrderAction
{
public function __invoke(User $user, CreateOrderData $data): Order
{
return DB::transaction(function () use ($user, $data) {
$order = $this->createOrder($user, $data);
$this->attachOrderItems($order, $data);
return $order->fresh(['items']);
});
}
private function createOrder(User $user, CreateOrderData $data): Order
{
return $user->orders()->create([
'status' => $data->status,
'notes' => $data->notes,
]);
}
private function attachOrderItems(Order $order, CreateOrderData $data): void
{
$order->items()->createMany(
$data->items->map(fn ($item) => [
'product_id' => $item->productId,
'quantity' => $item->quantity,
'price' => $item->price,
])->all()
);
}
}class CreateOrderAction
{
public function __construct(
private readonly CalculateOrderTotalAction $calculateTotal,
private readonly NotifyOrderCreatedAction $notifyOrderCreated,
) {}
public function __invoke(User $user, CreateOrderData $data): Order
{
return DB::transaction(function () use ($user, $data) {
$order = $this->createOrder($user, $data);
// Compose with other actions
$total = ($this->calculateTotal)($order);
$order->update(['total' => $total]);
($this->notifyOrderCreated)($order);
return $order->fresh();
});
}
}class CancelOrderAction
{
public function __invoke(Order $order): Order
{
$this->guard($order);
return DB::transaction(function () use ($order) {
$order->updateToCancelled();
$this->refundPayment($order);
return $order;
});
}
private function guard(Order $order): void
{
throw_unless(
$order->canBeCancelled(),
OrderException::cannotCancelOrder($order)
);
}
}public function __invoke(User $user, CreateApplicationData $data): Application
{
return DB::transaction(function () use ($user, $data) {
$application = $this->createApplication($user, $data);
$this->createContacts($application, $data);
$this->createAddresses($application, $data);
$this->createDocuments($application, $data);
return $application;
});
}class ProcessOrderAction
{
private readonly Order $order;
public function __invoke(Order $order): void
{
$this->order = $order;
$this->guard();
DB::transaction(function (): void {
$this->processPayment();
$this->updateInventory();
$this->sendNotifications();
});
}
private function guard(): void
{
throw_unless($this->order->isPending(), 'Order must be pending');
}
private function processPayment(): void
{
// Access $this->order without passing it
}
}{Verb}{Entity}ActionCreateOrderActionUpdateUserProfileActionDeleteDocumentActionCalculateOrderTotalActionSendEmailNotificationActionProcessPaymentAction$user->update($data)UpdateUserActionpublic function store(
CreateOrderRequest $request,
CreateOrderAction $action
) {
$order = $action(user(), CreateOrderData::from($request));
return OrderResource::make($order);
}resolve()// In controllers
$order = resolve(CreateOrderAction::class)(
user(),
CreateOrderData::from($request)
);
// Inside another action
$result = resolve(ProcessPaymentAction::class)($order, $paymentData);resolve()app()public function __invoke(CreateOrderData $data): Order
{
return DB::transaction(function () use ($data) {
$order = Order::create($data->toArray());
$order->items()->createMany($data->items->toArray());
return $order;
});
}class CreateOrderAction
{
public function __invoke(User $user, CreateOrderData $data): Order
{
if ($user->orders()->pending()->count() >= 5) {
throw OrderException::tooManyPendingOrders($user);
}
return DB::transaction(function () use ($user, $data) {
return $user->orders()->create($data->toArray());
});
}
}use function Pest\Laravel\assertDatabaseHas;
it('creates an order', function () {
// Arrange
$user = User::factory()->create();
$data = CreateOrderData::testFactory()->make();
// Act
$order = resolve(CreateOrderAction::class)($user, $data);
// Assert
expect($order)->toBeInstanceOf(Order::class);
assertDatabaseHas('orders', ['id' => $order->id]);
});
it('throws exception when user has too many pending orders', function () {
// Arrange
$user = User::factory()
->has(Order::factory()->pending()->count(5))
->create();
$data = CreateOrderData::testFactory()->make();
// Act & Assert
expect(fn () => resolve(CreateOrderAction::class)($user, $data))
->toThrow(OrderException::class);
});class UpdateUserAction
{
public function __invoke(User $user, UpdateUserData $data): User
{
$user->update($data->toArray());
return $user->fresh();
}
}class OnboardUserAction
{
public function __construct(
private readonly CreateUserProfileAction $createProfile,
private readonly SendWelcomeEmailAction $sendWelcome,
private readonly AssignDefaultRoleAction $assignRole,
) {}
public function __invoke(RegisterUserData $data): User
{
return DB::transaction(function () use ($data) {
$user = User::create($data->toArray());
($this->createProfile)($user, $data->profileData);
($this->assignRole)($user);
($this->sendWelcome)($user);
return $user;
});
}
}class ProcessPaymentAction
{
public function __construct(
private readonly StripeService $stripe,
) {}
public function __invoke(Order $order, PaymentData $data): Payment
{
$this->guard($order);
return DB::transaction(function () use ($order, $data) {
$stripePayment = $this->stripe->charge($data);
$payment = $order->payments()->create([
'amount' => $data->amount,
'stripe_id' => $stripePayment->id,
'status' => PaymentStatus::Completed,
]);
$order->markAsPaid();
return $payment;
});
}
private function guard(Order $order): void
{
throw_if($order->isPaid(), 'Order already paid');
}
}app/Actions/
├── Order/
│ ├── CreateOrderAction.php
│ ├── CancelOrderAction.php
│ ├── ProcessOrderAction.php
│ └── CalculateOrderTotalAction.php
├── User/
│ ├── CreateUserAction.php
│ ├── UpdateUserProfileAction.php
│ └── DeleteUserAction.php
└── Payment/
├── ProcessPaymentAction.php
└── RefundPaymentAction.phpapp/Actions/
├── Central/
│ ├── CreateTenantAction.php
│ └── ProvisionDatabaseAction.php
└── Tenanted/
├── CreateOrderAction.php
└── UpdateUserAction.php