laravel-api
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLaravel API - Steve's Architecture
Laravel API - Steve架构方案
Build Laravel REST APIs with clean, stateless, resource-scoped architecture.
基于简洁、无状态、资源范围化的架构构建Laravel REST API。
Quick Start
快速开始
When user requests a Laravel API, follow this workflow:
- Understand requirements - What resources? What operations? Authentication needed?
- Initialize project structure - Set up routing, remove frontend bloat
- Build first resource - Complete CRUD to establish pattern
- Add authentication - JWT via PHP Open Source Saver
- Iterate on remaining resources - Follow established pattern
当用户请求Laravel API相关服务时,请遵循以下工作流程:
- 理解需求 - 涉及哪些资源?需要哪些操作?是否需要认证?
- 初始化项目结构 - 配置路由,移除前端冗余内容
- 构建首个资源 - 完成CRUD操作以确立规范
- 添加认证功能 - 通过PHP Open Source Saver实现JWT认证
- 迭代剩余资源 - 遵循已确立的规范
Core Architecture Principles
核心架构原则
Read for comprehensive details. Key principles:
references/architecture.md- Stateless by design - No hidden dependencies, explicit data flow
- Boundary-first - Clear separation of HTTP, business logic, data layers
- Resource-scoped - Routes, controllers organized by resource
- Version discipline - Namespace-based versioning, HTTP Sunset headers
详细内容请查阅。核心原则:
references/architecture.md- 无状态设计 - 无隐藏依赖,数据流清晰明确
- 边界优先 - HTTP层、业务逻辑层、数据层清晰分离
- 资源范围化 - 路由、控制器按资源分类组织
- 版本规范 - 基于命名空间的版本控制,搭配HTTP Sunset响应头
Code Quality Standards
代码质量标准
All code must follow Laravel best practices and PSR-12 standards:
- Preserve Functionality - Refactorings change HOW code works, never WHAT it does
- Explicit Over Implicit - Prefer clear, readable code over clever shortcuts
- Type Declarations - Always use return types on methods, parameter types where beneficial
- Avoid Nested Ternaries - Use match expressions, switch, or if/else for clarity
- Consistent Naming - Follow PSR-12 and Laravel conventions strictly
- Proper Namespacing - Organize imports logically, use full type hints
When reviewing or refactoring code:
- Focus on clarity and maintainability over cleverness
- Simplify complex nested logic into readable structures
- Extract magic values into named constants or config
- Remove unnecessary complexity while preserving exact behavior
所有代码必须遵循Laravel最佳实践与PSR-12标准:
- 保留功能完整性 - 重构仅改变代码实现方式,绝不改变功能逻辑
- 显式优于隐式 - 优先选择清晰、可读的代码,而非巧妙的捷径
- 类型声明 - 方法必须添加返回类型,参数按需添加类型声明
- 避免嵌套三元运算符 - 使用match表达式、switch或if/else提升可读性
- 命名一致性 - 严格遵循PSR-12与Laravel命名规范
- 正确命名空间 - 逻辑组织导入,使用完整类型提示
评审或重构代码时:
- 优先关注代码清晰度与可维护性,而非技巧性
- 将复杂嵌套逻辑简化为可读结构
- 将魔法值提取为命名常量或配置项
- 在保留原有功能的前提下移除不必要的复杂度
Project Structure
项目结构
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 accessroutes/api/
routes.php # 主入口,版本分组
tasks.php # 所有任务路由,包含所有版本
projects.php # 所有项目路由,包含所有版本
app/Http/
Controllers/{Resource}/V1/
StoreController.php # 始终为可调用控制器
IndexController.php
ShowController.php
Requests/{Resource}/V1/
StoreTaskRequest.php # 验证 + payload()方法
Payloads/{Resource}/
StoreTaskPayload.php # 带toArray()的简单DTO
Responses/
JsonDataResponse.php # 实现Responsable接口
JsonErrorResponse.php
Middleware/
HttpSunset.php
app/Actions/{Resource}/
CreateTask.php # 单一职责的业务逻辑
app/Services/ # 仅当逻辑复杂度超出Action承载范围时使用
app/Models/
Task.php # 引入HasUlids trait,仅处理数据访问Building a New Resource Endpoint
构建新资源端点
Step 1: Model
步骤1:模型
Always use ULIDs. Keep models simple - data access only.
php
<?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);
}
}始终使用ULID。保持模型简洁 - 仅负责数据访问。
php
<?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);
}
}Step 2: Routes
步骤2:路由
Create resource route file at :
routes/api/{resource}.phpphp
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);
});Include in :
routes/api/routes.phpphp
Route::prefix('v1')->group(function () {
require __DIR__ . '/tasks.php';
});在创建资源路由文件:
routes/api/{resource}.phpphp
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.phpphp
Route::prefix('v1')->group(function () {
require __DIR__ . '/tasks.php';
});Step 3: DTO (Payload)
步骤3:DTO(Payload)
Create at :
app/Http/Payloads/{Resource}/{Operation}Payload.phpphp
<?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,
];
}
}在创建:
app/Http/Payloads/{Resource}/{Operation}Payload.phpphp
<?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,
];
}
}Step 4: Form Request
步骤4:Form Request
Create at :
app/Http/Requests/{Resource}/V1/{Operation}Request.phpphp
<?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(),
);
}
}在创建:
app/Http/Requests/{Resource}/V1/{Operation}Request.phpphp
<?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(),
);
}
}Step 5: Action
步骤5:Action
Create at :
app/Actions/{Resource}/{Operation}.phpphp
<?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());
}
}在创建:
app/Actions/{Resource}/{Operation}.phpphp
<?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());
}
}Step 6: Controller
步骤6:控制器
Create invokable controller at :
app/Http/Controllers/{Resource}/V1/{Operation}Controller.phpphp
<?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,
);
}
}在创建可调用控制器:
app/Http/Controllers/{Resource}/V1/{Operation}Controller.phpphp
<?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,
);
}
}Response Format
响应格式
Standard format for all responses:
Success:
json
{
"data": {...},
"meta": {...}
}Error (Problem+JSON):
json
{
"type": "about:blank",
"title": "Validation Failed",
"status": 422,
"detail": "The given data was invalid",
"errors": {...}
}所有响应采用标准格式:
成功响应:
json
{
"data": {...},
"meta": {...}
}错误响应(Problem+JSON):
json
{
"type": "about:blank",
"title": "Validation Failed",
"status": 422,
"detail": "The given data was invalid",
"errors": {...}
}Query Building
查询构建
Use Spatie Query Builder for filtering, sorting, includes:
php
use Spatie\QueryBuilder\QueryBuilder;
$tasks = QueryBuilder::for(Task::class)
->allowedFilters(['status', 'priority'])
->allowedSorts(['created_at', 'due_date'])
->allowedIncludes(['project', 'assignee'])
->paginate();使用Spatie Query Builder实现过滤、排序、关联查询:
php
use Spatie\QueryBuilder\QueryBuilder;
$tasks = QueryBuilder::for(Task::class)
->allowedFilters(['status', 'priority'])
->allowedSorts(['created_at', 'due_date'])
->allowedIncludes(['project', 'assignee'])
->paginate();Versioning Endpoints
端点版本控制
When creating V2:
- Create V2 namespace:
App\Http\Controllers\Tasks\V2\ - Add V2 route group in resource file
- Add Sunset middleware to V1 routes:
php
Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
// V1 routes
});创建V2版本时:
- 创建V2命名空间:
App\Http\Controllers\Tasks\V2\ - 在资源路由文件中添加V2路由分组
- 为V1路由添加Sunset中间件:
php
Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
// V1路由
});Authentication Setup
认证配置
Use PHP Open Source Saver JWT package:
bash
composer require php-open-source-saver/jwt-auth
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secretConfigure in :
config/auth.phpphp
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],使用PHP Open Source Saver JWT包:
bash
composer require php-open-source-saver/jwt-auth
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret在中配置:
config/auth.phpphp
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],Essential Setup
基础配置
Add to :
app/Providers/AppServiceProvider.phpphp
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::shouldBeStrict(); // Prevent N+1 queries
}Register HttpSunset middleware in :
app/Http/Kernel.phpphp
protected $middlewareAliases = [
'http.sunset' => \App\Http\Middleware\HttpSunset::class,
];在中添加:
app/Providers/AppServiceProvider.phpphp
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::shouldBeStrict(); // 防止N+1查询
}在中注册HttpSunset中间件:
app/Http/Kernel.phpphp
protected $middlewareAliases = [
'http.sunset' => \App\Http\Middleware\HttpSunset::class,
];Anti-Patterns to Avoid
需避免的反模式
- Using auto-increment IDs instead of ULIDs
- Business logic in models
- Multiple actions per controller
- Accessing request data directly in controllers/actions
- Hidden query scopes
- Service classes when an Action would suffice
- Breaking changes without versioning
- Inconsistent response formats
- Nested ternary operators (use match expressions instead)
- Missing type declarations on methods and parameters
- Overly compact "clever" code that sacrifices readability
- 使用自增ID而非ULID
- 在模型中编写业务逻辑
- 单个控制器处理多个操作
- 在控制器/Action中直接访问请求数据
- 隐藏查询作用域
- 可用Action实现却使用Service类
- 无版本控制的破坏性变更
- 不一致的响应格式
- 嵌套三元运算符(改用match表达式)
- 方法和参数缺少类型声明
- 过度追求简洁的“技巧性”代码而牺牲可读性
Code Review & Refactoring
代码评审与重构
When reviewing or refactoring Laravel API code, apply these principles:
评审或重构Laravel API代码时,遵循以下原则:
Simplification Checklist
简化检查清单
- Preserve Functionality - Ensure refactorings don't change behavior
- Check Type Safety - Add missing return types and parameter types
- Simplify Logic - Replace nested ternaries with match expressions
- Extract Complexity - Move complex conditions into named methods
- Verify Standards - Ensure PSR-12 compliance with declare(strict_types=1)
- Improve Naming - Use descriptive names that reveal intent
- 保留功能完整性 - 确保重构不改变原有功能
- 检查类型安全性 - 补充缺失的返回类型与参数类型
- 简化逻辑 - 用match表达式替代嵌套三元运算符
- 提取复杂逻辑 - 将复杂条件提取为命名方法
- 验证标准合规性 - 确保符合PSR-12规范,添加declare(strict_types=1)
- 优化命名 - 使用能体现意图的描述性命名
Match Expression Pattern
Match表达式模式
Replace nested ternaries with match for clarity:
php
// ❌ 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',
};用match表达式替代嵌套三元运算符以提升可读性:
php
// ❌ 避免:嵌套三元运算符
$status = $task->completed_at
? ($task->verified ? 'verified' : 'completed')
: ($task->started_at ? 'in_progress' : 'pending');
// ✅ 推荐:Match表达式
$status = match (true) {
$task->completed_at && $task->verified => 'verified',
$task->completed_at => 'completed',
$task->started_at => 'in_progress',
default => 'pending',
};References
参考文档
- architecture.md - Comprehensive architectural patterns and principles
- code-examples.md - Complete working examples for every component
- code-quality.md - Laravel best practices, refactoring patterns, and PSR-12 standards
- architecture.md - 完整的架构模式与原则
- code-examples.md - 所有组件的完整可运行示例
- code-quality.md - Laravel最佳实践、重构模式与PSR-12标准
Templates
模板
Template files in for quick scaffolding:
assets/templates/- Controller.php
- FormRequest.php
- Payload.php
- Action.php
- Model.php
assets/templates/- Controller.php
- FormRequest.php
- Payload.php
- Action.php
- Model.php