mcpField
L
laravel-api

laravel-api

github.com/juststeveking开发31288 次安装41 周前更新
代码仓库

关于 laravel-api

laravel-api 是用于构建 Laravel REST API 的开发技能,面向实现新 API 或审查现有代码的开发者。它强调无状态、按资源组织和清晰分层,并提供端点结构、编码规范及脚手架模板。

主要功能

  1. 01按资源组织路由和控制器
  2. 02采用无状态、分层的架构原则
  3. 03提供 CRUD 端点构建流程
  4. 04规范 API 版本管理与响应格式
  5. 05提供组件模板和代码审查准则

适用场景

  1. 01为 Laravel 项目搭建 REST API
  2. 02为资源添加版本化 CRUD 端点
  3. 03重构或审查 API 代码的一致性

安装

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

$npx skills add https://github.com/juststeveking/laravel-api-skill --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