mcpField
L
laravel-actions

laravel-actions

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

关于 laravel-actions

面向 Laravel 开发者的开发技能,将领域操作组织为专注单一职责的可调用 Action 类。它提供组合操作、事务处理和测试模式,帮助控制器、任务与监听器只负责委派业务逻辑。

主要功能

  1. 01单一职责的可调用 Action 类
  2. 02通过依赖注入组合 Action
  3. 03执行前检查业务规则
  4. 04在事务中执行数据修改
  5. 05Triple-A 单元测试模式

适用场景

  1. 01将领域逻辑移出控制器
  2. 02组合多步骤业务工作流
  3. 03以事务方式修改模型数据
  4. 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

示例:

  • 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