mcpField
L
laravel-actions

laravel-actions

github.com/leeovery开发4341 次安装31 周前更新
代码仓库

关于 laravel-actions

面向 Laravel 开发者的领域逻辑组织指南,主张将业务操作封装为单一职责、可组合的 Action,并让控制器、任务和监听器负责委派。内容涵盖事务、依赖注入、命名、异常处理与测试。

主要功能

  1. 01以可调用类封装业务操作
  2. 02通过依赖注入组合 Action
  3. 03使用守卫方法验证业务规则
  4. 04数据修改使用数据库事务
  5. 05按领域实体组织 Action
  6. 06采用 AAA 模式测试 Action

适用场景

  1. 01将控制器中的业务逻辑移至 Action
  2. 02组合多步骤业务工作流
  3. 03为数据修改添加事务保护
  4. 04统一 Laravel 项目的 Action 结构与测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/leeovery/claude-laravel --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

示例:

  • CreateOrderAction
  • UpdateUserProfileAction
  • DeleteDocumentAction
  • CalculateOrderTotalAction
  • SendEmailNotificationAction
  • ProcessPaymentAction

何时创建 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