laravel-actions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel 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
    __invoke()
    method
  • 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}Action
Examples:
  • CreateOrderAction
  • UpdateUserProfileAction
  • DeleteDocumentAction
  • CalculateOrderTotalAction
  • SendEmailNotificationAction
  • ProcessPaymentAction
格式:
{动词}{实体}Action
示例:
  • CreateOrderAction
  • UpdateUserProfileAction
  • DeleteDocumentAction
  • CalculateOrderTotalAction
  • SendEmailNotificationAction
  • ProcessPaymentAction

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
$user->update($data)
should be delegated to
UpdateUserAction
.
  • 仅用于展示的纯数据查询(使用查询/查询构建器)
  • HTTP相关的处理逻辑(属于中间件/控制器的职责)
  • 格式化/展示逻辑(使用资源/转换器)
关键规则: 控制器中应零领域逻辑。即使是简单的
$user->update($data)
也应该委托给
UpdateUserAction

Invocation 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()
助手函数

php
// In controllers
$order = resolve(CreateOrderAction::class)(
    user(),
    CreateOrderData::from($request)
);

// Inside another action
$result = resolve(ProcessPaymentAction::class)($order, $paymentData);
Important: Use
resolve()
not
app()
for consistency.
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.php
Not 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.php
See Multi-tenancy for comprehensive patterns.
分离中心和租户专属Actions:
app/Actions/
├── Central/
│   ├── CreateTenantAction.php
│   └── ProvisionDatabaseAction.php
└── Tenanted/
    ├── CreateOrderAction.php
    └── UpdateUserAction.php
详情请参阅多租户的完整模式。