关于 laravel-actions
面向 Laravel 开发者的开发技能,将领域操作组织为专注单一职责的可调用 Action 类。它提供组合操作、事务处理和测试模式,帮助控制器、任务与监听器只负责委派业务逻辑。
主要功能
- 01单一职责的可调用 Action 类
- 02通过依赖注入组合 Action
- 03执行前检查业务规则
- 04在事务中执行数据修改
- 05Triple-A 单元测试模式
适用场景
- 01将领域逻辑移出控制器
- 02组合多步骤业务工作流
- 03以事务方式修改模型数据
- 04测试独立业务操作
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ leeovery/ agentic-skills --skill laravel-actions安装后在 Claude Code 中输入/laravel-actions确认已加载。
详细介绍
Laravel Actions
Actions 是领域逻辑的核心。每项业务操作都由一个 Action 承载。
相关指南:
- DTOs - 用于向 Action 传递数据的 DTO
- Controllers - Controller 将任务委托给 Action
- Models - Action 访问的模型
- Testing - 使用 AAA 模式进行测试
理念
Controller、Job 和 Listener 不包含任何领域逻辑——它们只负责将任务委托给 Action。
Action 具有以下特点:
- 可调用类 - 只有一个
__invoke()方法 - 单一职责 - 每个 Action 只做一件事
- 可组合 - Action 可调用其他 Action 来构建工作流
- 无状态 - 每次调用相互独立(但可以存储调用上下文)
- 类型安全 - 参数和返回值类型严格
- 支持事务 - 在事务中执行数据库修改
基本结构
<?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()
);
}
}
关键模式
1. 使用依赖注入组合 Action
注入其他 Action,以构建复杂工作流:
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();
});
}
}
2. 使用 Guard 方法进行验证
执行操作前验证业务规则:
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)
);
}
}
3. 私有辅助方法
将复杂操作拆分为更小、更专注的私有方法:
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;
});
}
4. 使用只读属性保存上下文
将调用上下文存储在只读属性中,避免传递参数:
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}Action
示例:
CreateOrderActionUpdateUserProfileActionDeleteDocumentActionCalculateOrderTotalActionSendEmailNotificationActionProcessPaymentAction
何时创建 Action
✅ 以下情况应创建 Action:
- 任何领域操作(包括简单的 CRUD)
- 实现任何复杂程度的业务逻辑
- 构建可在多个位置复用的操作
- 将多个步骤组合成工作流
- Job 或 Listener 需要执行领域逻辑
- 任何涉及模型或数据的操作
❌ 以下情况不应创建 Action:
- 仅用于展示的数据查询(使用查询或查询构建器)
- HTTP 特定事项(应放在中间件或 Controller 中)
- 格式化或展示逻辑(使用 Resource 或 Transformer)
重要规则: Controller 不应包含任何领域逻辑。即使是简单的 $user->update($data),也应委托给 UpdateUserAction。
调用模式
通过依赖注入
public 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());
});
}
}
测试 Action
单元测试应使用 AAA 模式独立测试 Action:
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);
});
常见模式
简单 CRUD Action
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');
}
}
Action 的组织方式
按领域实体分组:
app/Actions/
├── Order/
│ ├── CreateOrderAction.php
│ ├── CancelOrderAction.php
│ ├── ProcessOrderAction.php
│ └── CalculateOrderTotalAction.php
├── User/
│ ├── CreateUserAction.php
│ ├── UpdateUserProfileAction.php
│ └── DeleteUserAction.php
└── Payment/
├── ProcessPaymentAction.php
└── RefundPaymentAction.php
不要按 Action 类型分组(避免使用 CreateActions/、UpdateActions/ 等目录)
多租户
将 Central 和 Tenanted Action 分开:
app/Actions/
├── Central/
│ ├── CreateTenantAction.php
│ └── ProvisionDatabaseAction.php
└── Tenanted/
├── CreateOrderAction.php
└── UpdateUserAction.php
全面的模式请参阅多租户。
评论
支持 Markdown