mcpField
L
laravel-api

laravel-api

github.com/noartem开发1381 次安装3 周前更新
代码仓库

关于 laravel-api

用于构建和审查 Laravel REST API 的技能,强调按资源组织、明确分层和端点版本管理,并以 Laravel 与 PSR-12 规范指导编码 Agent 实现和重构。

主要功能

  1. 01按资源组织 REST API
  2. 02无状态分层架构
  3. 03版本化路由与 Sunset 标头
  4. 04基于 ULID 的模型和 CRUD 模式
  5. 05PSR-12 与类型安全规范
  6. 06脚手架模板与参考资料

适用场景

  1. 01搭建新的 Laravel API 资源
  2. 02重构控制器与业务逻辑
  3. 03添加端点版本和弃用标头

安装

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

$npx skills add https://github.com/noartem/skills --skill laravel-api

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/templates/
Action.php
Controller.php
FormRequest.php
Model.php
Payload.php
references/
architecture.md
code-examples.md
code-quality.md

详细介绍

Laravel API - Steve 的架构

使用简洁、无状态、按资源划分的架构构建 Laravel REST API。

快速开始

用户请求构建 Laravel API 时,遵循以下工作流:

  1. 了解需求 - 有哪些资源?需要哪些操作?是否需要身份验证?
  2. 初始化项目结构 - 配置路由,移除前端冗余内容
  3. 构建第一个资源 - 完成 CRUD,确立模式
  4. 添加身份验证 - 使用 PHP Open Source Saver 实现 JWT
  5. 迭代其余资源 - 遵循已确立的模式

核心架构原则

阅读 references/architecture.md 了解详情。主要原则:

  1. 设计为无状态 - 不依赖隐藏内容,数据流明确
  2. 边界优先 - 清晰划分 HTTP、业务逻辑和数据层
  3. 按资源划分 - 按资源组织路由和控制器
  4. 严格遵守版本规范 - 基于命名空间进行版本控制,使用 HTTP Sunset 标头

代码质量标准

所有代码都必须遵循 Laravel 最佳实践和 PSR-12 标准:

  1. 保留功能 - 重构只改变代码的工作方式,绝不改变代码的功能
  2. 明确优于隐式 - 优先编写清晰易读的代码,而不是取巧的简写
  3. 类型声明 - 方法始终声明返回类型,并在适用时声明参数类型
  4. 避免嵌套三元运算符 - 使用 match 表达式、switch 或 if/else 提高清晰度
  5. 命名一致 - 严格遵循 PSR-12 和 Laravel 约定
  6. 恰当使用命名空间 - 合理组织导入项,使用完整类型提示

审查或重构代码时:

  • 优先考虑清晰度和可维护性,而非取巧
  • 将复杂的嵌套逻辑简化为易读的结构
  • 将魔法值提取为具名常量或配置项
  • 在保持行为完全一致的同时,去除不必要的复杂性

项目结构

routes/api/
  routes.php              # Main entry point, version grouping
  tasks.php               # All task routes, all versions
  projects.php            # All project routes, all versions

app/Http/
  Controllers/{Resource}/V1/
    StoreController.php   # Always invokable
    IndexController.php
    ShowController.php
  Requests/{Resource}/V1/
    StoreTaskRequest.php  # Validation + payload() method
  Payloads/{Resource}/
    StoreTaskPayload.php  # Simple DTOs with toArray()
  Responses/
    JsonDataResponse.php  # Implements Responsable
    JsonErrorResponse.php
  Middleware/
    HttpSunset.php

app/Actions/{Resource}/
  CreateTask.php          # Single-purpose business logic

app/Services/             # Only when logic too complex for Actions

app/Models/
  Task.php                # HasUlids trait, simple data access

构建新的资源端点

第 1 步:模型

始终使用 ULID。保持模型简洁——仅负责数据访问。

<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

final class Task extends Model
{
    use HasFactory;
    use HasUlids;
    
    protected $fillable = [
        'title',
        'description',
        'status',
        'project_id',
    ];

    protected $casts = [
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    public function project(): BelongsTo
    {
        return $this->belongsTo(Project::class);
    }
}
第 2 步:路由

在 routes/api/{resource}.php 创建资源路由文件:

use App\Http\Controllers\Tasks\V1;

Route::middleware(['auth:api'])->group(function () {
    Route::get('/tasks', V1\IndexController::class);
    Route::post('/tasks', V1\StoreController::class);
    Route::get('/tasks/{task}', V1\ShowController::class);
    Route::patch('/tasks/{task}', V1\UpdateController::class);
    Route::delete('/tasks/{task}', V1\DestroyController::class);
});

在 routes/api/routes.php 中引入:

Route::prefix('v1')->group(function () {
    require __DIR__ . '/tasks.php';
});
第 3 步:DTO(负载)

在 app/Http/Payloads/{Resource}/{Operation}Payload.php 创建:

<?php

declare(strict_types=1);

namespace App\Http\Payloads\Tasks;

final readonly class StoreTaskPayload
{
    public function __construct(
        public string $title,
        public ?string $description,
        public string $status,
        public string $projectId,
    ) {}

    public function toArray(): array
    {
        return [
            'title' => $this->title,
            'description' => $this->description,
            'status' => $this->status,
            'project_id' => $this->projectId,
        ];
    }
}
第 4 步:表单请求

在 app/Http/Requests/{Resource}/V1/{Operation}Request.php 创建:

<?php

declare(strict_types=1);

namespace App\Http\Requests\Tasks\V1;

use App\Http\Payloads\Tasks\StoreTaskPayload;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

final class StoreTaskRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string', 'max:1000'],
            'status' => ['required', Rule::in(['pending', 'in_progress', 'completed'])],
            'project_id' => ['required', 'string', 'exists:projects,id'],
        ];
    }

    public function payload(): StoreTaskPayload
    {
        return new StoreTaskPayload(
            title: $this->string('title')->toString(),
            description: $this->string('description')->toString(),
            status: $this->string('status')->toString(),
            projectId: $this->string('project_id')->toString(),
        );
    }
}
第 5 步:Action

在 app/Actions/{Resource}/{Operation}.php 创建:

<?php

declare(strict_types=1);

namespace App\Actions\Tasks;

use App\Http\Payloads\Tasks\StoreTaskPayload;
use App\Models\Task;

final readonly class CreateTask
{
    public function handle(StoreTaskPayload $payload): Task
    {
        return Task::create($payload->toArray());
    }
}
第 6 步:控制器

在 app/Http/Controllers/{Resource}/V1/{Operation}Controller.php 创建可调用控制器:

<?php

declare(strict_types=1);

namespace App\Http\Controllers\Tasks\V1;

use App\Actions\Tasks\CreateTask;
use App\Http\Requests\Tasks\V1\StoreTaskRequest;
use App\Http\Responses\JsonDataResponse;
use Illuminate\Http\JsonResponse;

final readonly class StoreController
{
    public function __construct(
        private CreateTask $createTask,
    ) {}

    public function __invoke(StoreTaskRequest $request): JsonResponse
    {
        $task = $this->createTask->handle(
            payload: $request->payload(),
        );

        return new JsonDataResponse(
            data: $task,
            status: 201,
        );
    }
}

响应格式

所有响应的标准格式:

成功:

{
    "data": {...},
    "meta": {...}
}

错误(Problem+JSON):

{
    "type": "about:blank",
    "title": "Validation Failed",
    "status": 422,
    "detail": "The given data was invalid",
    "errors": {...}
}

查询构建

使用 Spatie Query Builder 进行筛选、排序和包含关联数据:

use Spatie\QueryBuilder\QueryBuilder;

$tasks = QueryBuilder::for(Task::class)
    ->allowedFilters(['status', 'priority'])
    ->allowedSorts(['created_at', 'due_date'])
    ->allowedIncludes(['project', 'assignee'])
    ->paginate();

端点版本控制

创建 V2 时:

  1. 创建 V2 命名空间:App\Http\Controllers\Tasks\V2\
  2. 在资源文件中添加 V2 路由组
  3. 为 V1 路由添加 Sunset 中间件:
Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
    // V1 routes
});

身份验证设置

使用 PHP Open Source Saver JWT 包:

composer require php-open-source-saver/jwt-auth
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret

在 config/auth.php 中配置:

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

必要设置

添加到 app/Providers/AppServiceProvider.php:

use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::shouldBeStrict(); // Prevent N+1 queries
}

在 app/Http/Kernel.php 中注册 HttpSunset 中间件:

protected $middlewareAliases = [
    'http.sunset' => \App\Http\Middleware\HttpSunset::class,
];

应避免的反模式

  • 使用自增 ID 而不是 ULID
  • 在模型中编写业务逻辑
  • 一个控制器中包含多个 Action
  • 在控制器或 Action 中直接访问请求数据
  • 隐式查询作用域
  • 可以用 Action 实现时却使用服务类
  • 不进行版本控制就引入破坏性变更
  • 响应格式不一致
  • 使用嵌套三元运算符(改用 match 表达式)
  • 方法和参数缺少类型声明
  • 过度压缩的“巧妙”代码牺牲了可读性

代码审查与重构

审查或重构 Laravel API 代码时,应用以下原则:

简化检查清单
  1. 保留功能 - 确保重构不会改变行为
  2. 检查类型安全 - 补充缺失的返回类型和参数类型
  3. 简化逻辑 - 将嵌套三元表达式替换为 match 表达式
  4. 提取复杂逻辑 - 将复杂条件移入命名方法
  5. 验证规范 - 确保符合 PSR-12,并包含 declare(strict_types=1)
  6. 改进命名 - 使用能体现意图的描述性名称
Match 表达式模式

使用 match 替换嵌套三元表达式,以提升清晰度:

// ❌ Avoid: Nested ternary
$status = $task->completed_at 
    ? ($task->verified ? 'verified' : 'completed')
    : ($task->started_at ? 'in_progress' : 'pending');

// ✅ Prefer: Match expression
$status = match (true) {
    $task->completed_at && $task->verified => 'verified',
    $task->completed_at => 'completed',
    $task->started_at => 'in_progress',
    default => 'pending',
};

参考资料

  • architecture.md - 全面的架构模式和原则
  • code-examples.md - 每个组件的完整可运行示例
  • code-quality.md - Laravel 最佳实践、重构模式和 PSR-12 规范

模板

assets/templates/ 中的模板文件可用于快速搭建:

  • Controller.php
  • FormRequest.php
  • Payload.php
  • Action.php
  • Model.php

评论

支持 Markdown