laravel-actions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLaravel Actions
Laravel Actions
Actions are the heart of your domain logic. Every business operation lives in an action.
Related guides:
- DTOs - DTOs for passing data to actions
- Controllers - Controllers delegate to actions
- Models - Models accessed by actions
- Testing - Testing with triple-A pattern
Actions是领域逻辑的核心。每一项业务操作都在一个Action中实现。
相关指南:
- DTOs - 用于向Actions传递数据的DTOs
- Controllers - 委托给Actions的控制器
- Models - Actions访问的模型
- Testing - 采用AAA模式的测试
Philosophy
设计理念
Controllers, Jobs, and Listeners contain ZERO domain logic - they only delegate to actions.
Actions are:
- Invokable classes - Single method
__invoke() - Single responsibility - Each action does exactly one thing
- Composable - Actions call other actions to build workflows
- Stateless - Each invocation is independent (but can store invocation context)
- Type-safe - Strict parameter and return types
- Transactional - Wrap database modifications in transactions
控制器、任务(Jobs)和监听器(Listeners)不包含任何领域逻辑——它们仅负责委托给Actions。
Actions具备以下特性:
- 可调用类 - 单个方法
__invoke() - 单一职责 - 每个Action只完成一件事
- 可组合 - Actions可以调用其他Actions来构建工作流
- 无状态 - 每次调用都是独立的(但可以存储调用上下文)
- 类型安全 - 严格的参数和返回类型
- 事务性 - 将数据库修改操作包裹在事务中
Basic Structure
基本结构
php
<?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()
);
}
}php
<?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()
);
}
}Key Patterns
核心模式
1. Dependency Injection for Action Composition
1. 用于Action组合的依赖注入
Inject other actions to build complex workflows:
php
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();
});
}
}注入其他Actions来构建复杂工作流:
php
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);
// 与其他Actions组合
$total = ($this->calculateTotal)($order);
$order->update(['total' => $total]);
($this->notifyOrderCreated)($order);
return $order->fresh();
});
}
}2. Guard Methods for Validation
2. 用于验证的守卫方法
Validate business rules before executing:
php
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)
);
}
}在执行前验证业务规则:
php
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. Private Helper Methods
3. 私有辅助方法
Break complex operations into smaller, focused private methods:
php
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;
});
}将复杂操作拆分为更小、聚焦的私有方法:
php
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. Readonly Properties for Context
4. 用于上下文的只读属性
Store invocation context in readonly properties to avoid parameter passing:
php
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
}
}将调用上下文存储在只读属性中,避免参数传递:
php
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
{
// 无需传递即可访问$this->order
}
}Naming Conventions
命名规范
Format:
{Verb}{Entity}ActionExamples:
CreateOrderActionUpdateUserProfileActionDeleteDocumentActionCalculateOrderTotalActionSendEmailNotificationActionProcessPaymentAction
格式:
{动词}{实体}Action示例:
CreateOrderActionUpdateUserProfileActionDeleteDocumentActionCalculateOrderTotalActionSendEmailNotificationActionProcessPaymentAction
When to Create an Action
何时创建Action
✅ Create an action when:
✅ 以下场景建议创建Action:
- Any domain operation (including simple CRUD)
- Implementing business logic of any complexity
- Building reusable operations used across multiple places
- Composing multiple steps into a workflow
- Job or listener needs to perform domain logic
- Any operation that touches your models or data
- 任何领域操作(包括简单的CRUD)
- 实现任意复杂度的业务逻辑
- 构建可在多个地方复用的操作
- 将多个步骤组合成工作流
- 任务(Job)或监听器(Listener)需要执行领域逻辑
- 任何涉及模型或数据的操作
❌ Don't create an action for:
❌ 以下场景无需创建Action:
- Pure data retrieval for display (use queries/query builders)
- HTTP-specific concerns (belongs in middleware/controllers)
- Formatting/presentation logic (use resources/transformers)
Critical Rule: Controllers should contain zero domain logic. Even a simple should be delegated to .
$user->update($data)UpdateUserAction- 仅用于展示的纯数据查询(使用查询/查询构建器)
- HTTP相关的处理逻辑(属于中间件/控制器的职责)
- 格式化/展示逻辑(使用资源/转换器)
关键规则: 控制器中应零领域逻辑。即使是简单的也应该委托给。
$user->update($data)UpdateUserActionInvocation Patterns
调用模式
Via Dependency Injection
通过依赖注入
php
public function store(
CreateOrderRequest $request,
CreateOrderAction $action
) {
$order = $action(user(), CreateOrderData::from($request));
return OrderResource::make($order);
}php
public function store(
CreateOrderRequest $request,
CreateOrderAction $action
) {
$order = $action(user(), CreateOrderData::from($request));
return OrderResource::make($order);
}Via resolve()
Helper
resolve()通过resolve()
助手函数
resolve()php
// In controllers
$order = resolve(CreateOrderAction::class)(
user(),
CreateOrderData::from($request)
);
// Inside another action
$result = resolve(ProcessPaymentAction::class)($order, $paymentData);Important: Use not for consistency.
resolve()app()php
// 在控制器中
$order = resolve(CreateOrderAction::class)(
user(),
CreateOrderData::from($request)
);
// 在另一个Action内部
$result = resolve(ProcessPaymentAction::class)($order, $paymentData);重要提示: 为了一致性,请使用而非。
resolve()app()Database Transactions
数据库事务
Always wrap data modifications in transactions:
php
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;
});
}始终将数据修改操作包裹在事务中:
php
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;
});
}Error Handling
错误处理
Throw domain exceptions for business rule violations:
php
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());
});
}
}针对业务规则违规抛出领域异常:
php
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());
});
}
}Testing Actions
测试Actions
Unit tests should test actions in isolation using the triple-A pattern:
php
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);
});单元测试应采用AAA模式独立测试Actions:
php
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);
});Common Patterns
常见模式
Simple CRUD Action
简单CRUD Action
php
class UpdateUserAction
{
public function __invoke(User $user, UpdateUserData $data): User
{
$user->update($data->toArray());
return $user->fresh();
}
}php
class UpdateUserAction
{
public function __invoke(User $user, UpdateUserData $data): User
{
$user->update($data->toArray());
return $user->fresh();
}
}Multi-Step Workflow
多步骤工作流
php
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;
});
}
}php
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;
});
}
}External Service Integration
外部服务集成
php
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');
}
}php
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 Organization
Action组织方式
Group by domain entity:
app/Actions/
├── Order/
│ ├── CreateOrderAction.php
│ ├── CancelOrderAction.php
│ ├── ProcessOrderAction.php
│ └── CalculateOrderTotalAction.php
├── User/
│ ├── CreateUserAction.php
│ ├── UpdateUserProfileAction.php
│ └── DeleteUserAction.php
└── Payment/
├── ProcessPaymentAction.php
└── RefundPaymentAction.phpNot by action type (avoid CreateActions/, UpdateActions/, etc.)
按领域实体分组:
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/等结构)
Multi-Tenancy
多租户
Separate Central and Tenanted actions:
app/Actions/
├── Central/
│ ├── CreateTenantAction.php
│ └── ProvisionDatabaseAction.php
└── Tenanted/
├── CreateOrderAction.php
└── UpdateUserAction.phpSee Multi-tenancy for comprehensive patterns.
分离中心和租户专属Actions:
app/Actions/
├── Central/
│ ├── CreateTenantAction.php
│ └── ProvisionDatabaseAction.php
└── Tenanted/
├── CreateOrderAction.php
└── UpdateUserAction.php详情请参阅多租户的完整模式。