mcpField
L
laravel-patterns

laravel-patterns

github.com/midudev开发7.0k58 次安装1 周前更新
代码仓库

关于 laravel-patterns

面向 Laravel 应用和 API 开发的模式技能,帮助开发者组织应用分层与领域逻辑。内容通过具体约定和示例涵盖路由、Eloquent、验证、队列、缓存及配置。

主要功能

  1. 01拆分控制器、服务和操作类
  2. 02使用作用域路由模型绑定
  3. 03采用类型化 Eloquent 模型与作用域
  4. 04规范 API 资源和分页
  5. 05处理队列、事件与缓存
  6. 06按环境组织配置

适用场景

  1. 01组织 Laravel API 或 Web 应用
  2. 02通过嵌套路由防止跨租户访问
  3. 03避免 N+1 查询并进行分页
  4. 04将耗时任务交由队列处理

安装

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

$npx skills add https://github.com/midudev/autoskills --skill laravel-patterns

安装后在 Claude Code 中输入/laravel-patterns确认已加载。

详细介绍

Laravel 开发模式

适用于可扩展、易维护应用的生产级 Laravel 架构模式。

适用场景

  • 构建 Laravel Web 应用或 API
  • 组织控制器、服务和领域逻辑
  • 使用 Eloquent 模型及其关系
  • 使用资源和分页设计 API
  • 添加队列、事件、缓存和后台作业

工作原理

  • 围绕清晰的边界组织应用(控制器 -> 服务/操作 -> 模型)。
  • 使用显式绑定和作用域绑定,让路由保持可预测;同时仍需通过授权实施访问控制。
  • 优先使用带类型的模型、类型转换和作用域,让领域逻辑保持一致。
  • 将 IO 密集型工作放入队列,并缓存开销较大的读取操作。
  • 将配置集中放在 config/* 中,并明确区分各环境。

示例

项目结构

使用常规的 Laravel 目录结构,并明确划分各层(HTTP、服务/操作、模型)。

推荐布局
app/
├── Actions/            # Single-purpose use cases
├── Console/
├── Events/
├── Exceptions/
├── Http/
│   ├── Controllers/
│   ├── Middleware/
│   ├── Requests/       # Form request validation
│   └── Resources/      # API resources
├── Jobs/
├── Models/
├── Policies/
├── Providers/
├── Services/           # Coordinating domain services
└── Support/
config/
database/
├── factories/
├── migrations/
└── seeders/
resources/
├── views/
└── lang/
routes/
├── api.php
├── web.php
└── console.php
控制器 -> 服务 -> 操作

保持控制器精简。将流程编排放在服务中,并将单一职责逻辑放在操作中。

final class CreateOrderAction
{
    public function __construct(private OrderRepository $orders) {}

    public function handle(CreateOrderData $data): Order
    {
        return $this->orders->create($data);
    }
}

final class OrdersController extends Controller
{
    public function __construct(private CreateOrderAction $createOrder) {}

    public function store(StoreOrderRequest $request): JsonResponse
    {
        $order = $this->createOrder->handle($request->toDto());

        return response()->json([
            'success' => true,
            'data' => OrderResource::make($order),
            'error' => null,
            'meta' => null,
        ], 201);
    }
}
路由和控制器

优先使用路由模型绑定和资源控制器,以提高代码清晰度。

use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('projects', ProjectController::class);
});
路由模型绑定(作用域)

使用作用域绑定,防止跨租户访问。

Route::scopeBindings()->group(function () {
    Route::get('/accounts/{account}/projects/{project}', [ProjectController::class, 'show']);
});
嵌套路由和绑定名称
  • 保持前缀和路径一致,避免重复嵌套(例如,conversation 和 conversations)。
  • 使用与绑定模型匹配的单一参数名称(例如,Conversation 对应 {conversation})。
  • 嵌套时优先使用作用域绑定,以强制执行父子关系。
use App\Http\Controllers\Api\ConversationController;
use App\Http\Controllers\Api\MessageController;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->prefix('conversations')->group(function () {
    Route::post('/', [ConversationController::class, 'store'])->name('conversations.store');

    Route::scopeBindings()->group(function () {
        Route::get('/{conversation}', [ConversationController::class, 'show'])
            ->name('conversations.show');

        Route::post('/{conversation}/messages', [MessageController::class, 'store'])
            ->name('conversation-messages.store');

        Route::get('/{conversation}/messages/{message}', [MessageController::class, 'show'])
            ->name('conversation-messages.show');
    });
});

如果希望参数解析为不同的模型类,请定义显式绑定。对于自定义绑定逻辑,请使用 Route::bind(),或在模型上实现 resolveRouteBinding()。

use App\Models\AiConversation;
use Illuminate\Support\Facades\Route;

Route::model('conversation', AiConversation::class);
服务容器绑定

在服务提供者中将接口绑定到实现,以明确依赖关系的连接方式。

use App\Repositories\EloquentOrderRepository;
use App\Repositories\OrderRepository;
use Illuminate\Support\ServiceProvider;

final class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(OrderRepository::class, EloquentOrderRepository::class);
    }
}
Eloquent 模型模式
模型配置
final class Project extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'owner_id', 'status'];

    protected $casts = [
        'status' => ProjectStatus::class,
        'archived_at' => 'datetime',
    ];

    public function owner(): BelongsTo
    {
        return $this->belongsTo(User::class, 'owner_id');
    }

    public function scopeActive(Builder $query): Builder
    {
        return $query->whereNull('archived_at');
    }
}
自定义类型转换和包装值对象

使用枚举或包装值对象实现严格类型检查。

use Illuminate\Database\Eloquent\Casts\Attribute;

protected $casts = [
    'status' => ProjectStatus::class,
];
protected function budgetCents(): Attribute
{
    return Attribute::make(
        get: fn (int $value) => Money::fromCents($value),
        set: fn (Money $money) => $money->toCents(),
    );
}
预加载以避免 N+1 问题
$orders = Order::query()
    ->with(['customer', 'items.product'])
    ->latest()
    ->paginate(25);
复杂筛选的查询对象
final class ProjectQuery
{
    public function __construct(private Builder $query) {}

    public function ownedBy(int $userId): self
    {
        $query = clone $this->query;

        return new self($query->where('owner_id', $userId));
    }

    public function active(): self
    {
        $query = clone $this->query;

        return new self($query->whereNull('archived_at'));
    }

    public function builder(): Builder
    {
        return $this->query;
    }
}
全局作用域和软删除

使用全局作用域进行默认筛选,并使用 SoftDeletes 保留可恢复记录。 对于同一筛选条件,请使用全局作用域或命名作用域,而不要同时使用,除非你有意采用分层行为。

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;

final class Project extends Model
{
    use SoftDeletes;

    protected static function booted(): void
    {
        static::addGlobalScope('active', function (Builder $builder): void {
            $builder->whereNull('archived_at');
        });
    }
}
可复用筛选的查询作用域
use Illuminate\Database\Eloquent\Builder;

final class Project extends Model
{
    public function scopeOwnedBy(Builder $query, int $userId): Builder
    {
        return $query->where('owner_id', $userId);
    }
}

// In service, repository etc.
$projects = Project::ownedBy($user->id)->get();
多步骤更新的事务
use Illuminate\Support\Facades\DB;

DB::transaction(function (): void {
    $order->update(['status' => 'paid']);
    $order->items()->update(['paid_at' => now()]);
});
迁移
命名约定
  • 文件名使用时间戳:YYYY_MM_DD_HHMMSS_create_users_table.php
  • 迁移使用匿名类(不使用命名类);文件名应体现其用途
  • 表名默认使用复数形式的 snake_case
迁移示例
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('customer_id')->constrained()->cascadeOnDelete();
            $table->string('status', 32)->index();
            $table->unsignedInteger('total_cents');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
};
表单请求和验证

将验证逻辑放在表单请求中,并将输入转换为 DTO。

use App\Models\Order;

final class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()?->can('create', Order::class) ?? false;
    }

    public function rules(): array
    {
        return [
            'customer_id' => ['required', 'integer', 'exists:customers,id'],
            'items' => ['required', 'array', 'min:1'],
            'items.*.sku' => ['required', 'string'],
            'items.*.quantity' => ['required', 'integer', 'min:1'],
        ];
    }

    public function toDto(): CreateOrderData
    {
        return new CreateOrderData(
            customerId: (int) $this->validated('customer_id'),
            items: $this->validated('items'),
        );
    }
}
API 资源

使用资源和分页,让 API 响应保持一致。

$projects = Project::query()->active()->paginate(25);

return response()->json([
    'success' => true,
    'data' => ProjectResource::collection($projects->items()),
    'error' => null,
    'meta' => [
        'page' => $projects->currentPage(),
        'per_page' => $projects->perPage(),
        'total' => $projects->total(),
    ],
]);
事件、作业和队列
  • 针对副作用(电子邮件、分析)发出领域事件
  • 对耗时工作(报告、导出、Webhook)使用队列作业
  • 优先使用支持重试和退避的幂等处理程序
缓存
  • 缓存读取频繁的端点和开销较大的查询
  • 在模型事件(创建/更新/删除)发生时使缓存失效
  • 缓存相关数据时使用标签,以便轻松使其失效
配置和环境
  • 将密钥保存在 .env 中,将配置保存在 config/*.php 中
  • 使用按环境设置的配置覆盖,并在生产环境中使用 config:cache

评论

支持 Markdown