laravel-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel 12 Best Practices

Laravel 12 最佳实践

Comprehensive best practices guide for Laravel 12 applications. Contains 45+ rules across 8 categories for building scalable, maintainable Laravel applications.
这是一份针对Laravel 12应用的全面最佳实践指南,包含8个类别下的45+条规则,助力构建可扩展、可维护的Laravel应用。

When to Apply

适用场景

Reference these guidelines when:
  • Creating controllers, models, and services
  • Writing migrations and database queries
  • Implementing validation and form requests
  • Building APIs with Laravel
  • Structuring Laravel applications
在以下场景中可参考本指南:
  • 创建控制器、模型和服务时
  • 编写迁移和数据库查询时
  • 实现验证和表单请求时
  • 使用Laravel构建API时
  • 构建Laravel应用架构时

Rule Categories by Priority

按优先级划分的规则类别

PriorityCategoryImpactPrefix
1Architecture & StructureCRITICAL
arch-
2Eloquent & DatabaseCRITICAL
eloquent-
3Controllers & RoutingHIGH
controller-
,
ctrl-
4Validation & RequestsHIGH
validation-
,
valid-
5SecurityHIGH
sec-
6PerformanceMEDIUM
perf-
7API DesignMEDIUM
api-
优先级类别影响程度前缀
1架构与结构关键
arch-
2Eloquent与数据库关键
eloquent-
3控制器与路由
controller-
,
ctrl-
4验证与请求
validation-
,
valid-
5安全
sec-
6性能
perf-
7API设计
api-

Quick Reference

快速参考

1. Architecture & Structure (CRITICAL)

1. 架构与结构(关键)

  • arch-service-classes
    - Extract business logic to services
  • arch-action-classes
    - Single-purpose action classes
  • arch-repository-pattern
    - When to use repositories
  • arch-dto-pattern
    - Data transfer objects
  • arch-value-objects
    - Encapsulate domain concepts
  • arch-event-driven
    - Decouple with events and listeners
  • arch-feature-folders
    - Organize by domain/feature
  • arch-service-classes
    - 将业务逻辑提取到服务层
  • arch-action-classes
    - 单一职责的动作类
  • arch-repository-pattern
    - 仓库模式的适用场景
  • arch-dto-pattern
    - 数据传输对象
  • arch-value-objects
    - 封装领域概念
  • arch-event-driven
    - 利用事件和监听器实现解耦
  • arch-feature-folders
    - 按领域/功能组织代码

2. Eloquent & Database (CRITICAL)

2. Eloquent与数据库(关键)

  • eloquent-eager-loading
    - Prevent N+1 queries
  • eloquent-chunking
    - Process large datasets
  • eloquent-query-scopes
    - Reusable query logic
  • eloquent-model-events
    - Use observers for side effects
  • eloquent-relationships
    - Define relationships properly
  • eloquent-casts
    - Automatic attribute casting
  • eloquent-accessors-mutators
    - Transform attributes
  • eloquent-soft-deletes
    - Safe deletion with recovery
  • eloquent-pruning
    - Automatic cleanup of old records
  • eloquent-eager-loading
    - 避免N+1查询问题
  • eloquent-chunking
    - 处理大型数据集
  • eloquent-query-scopes
    - 可复用的查询逻辑
  • eloquent-model-events
    - 使用观察者处理副作用
  • eloquent-relationships
    - 正确定义关联关系
  • eloquent-casts
    - 自动属性类型转换
  • eloquent-accessors-mutators
    - 属性转换处理
  • eloquent-soft-deletes
    - 安全删除与恢复
  • eloquent-pruning
    - 自动清理旧记录

3. Controllers & Routing (HIGH)

3. 控制器与路由(高)

  • ctrl-resource-controllers
    - Use resource controllers
  • controller-single-action
    - Single action invokable controllers
  • controller-resource-methods
    - RESTful resource methods
  • controller-form-requests
    - Use form requests
  • controller-api-resources
    - Transform API responses
  • controller-middleware
    - Apply middleware properly
  • controller-dependency-injection
    - Inject dependencies
  • ctrl-resource-controllers
    - 使用资源控制器
  • controller-single-action
    - 单一动作的可调用控制器
  • controller-resource-methods
    - RESTful资源方法
  • controller-form-requests
    - 使用表单请求
  • controller-api-resources
    - 转换API响应
  • controller-middleware
    - 正确应用中间件
  • controller-dependency-injection
    - 依赖注入

4. Validation & Requests (HIGH)

4. 验证与请求(高)

  • validation-form-requests
    - Use form request classes
  • validation-custom-rules
    - Create custom rules
  • validation-conditional-rules
    - Conditional validation
  • validation-array-validation
    - Validate nested arrays
  • validation-after-hooks
    - Complex validation logic
  • validation-form-requests
    - 使用表单请求类
  • validation-custom-rules
    - 创建自定义验证规则
  • validation-conditional-rules
    - 条件验证
  • validation-array-validation
    - 嵌套数组验证
  • validation-after-hooks
    - 复杂验证逻辑

5. Security (HIGH)

5. 安全(高)

  • sec-mass-assignment
    - Protect against mass assignment
  • Additional security rules can be added as needed
  • sec-mass-assignment
    - 防范批量赋值漏洞
  • 可根据需要添加更多安全规则

6. Performance (MEDIUM)

6. 性能(中)

  • Performance rules can be added for caching, queues, and optimization
  • 可添加缓存、队列和优化相关的性能规则

7. API Design (MEDIUM)

7. API设计(中)

  • API design rules can be added for versioning and response formatting
  • 可添加版本控制和响应格式相关的API设计规则

Essential Patterns

核心模式

Controller with Form Request

结合表单请求的控制器

php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;

class PostController extends Controller
{
    public function store(StorePostRequest $request): RedirectResponse
    {
        // Validation happens automatically
        $validated = $request->validated();

        $post = Post::create($validated);

        return redirect()
            ->route('posts.show', $post)
            ->with('success', 'Post created successfully.');
    }

    public function update(UpdatePostRequest $request, Post $post): RedirectResponse
    {
        $post->update($request->validated());

        return redirect()
            ->route('posts.show', $post)
            ->with('success', 'Post updated successfully.');
    }
}
php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;

class PostController extends Controller
{
    public function store(StorePostRequest $request): RedirectResponse
    {
        // 验证自动执行
        $validated = $request->validated();

        $post = Post::create($validated);

        return redirect()
            ->route('posts.show', $post)
            ->with('success', 'Post created successfully.');
    }

    public function update(UpdatePostRequest $request, Post $post): RedirectResponse
    {
        $post->update($request->validated());

        return redirect()
            ->route('posts.show', $post)
            ->with('success', 'Post updated successfully.');
    }
}

Form Request Class

表单请求类

php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string', 'min:100'],
            'category_id' => ['required', 'exists:categories,id'],
            'tags' => ['nullable', 'array'],
            'tags.*' => ['exists:tags,id'],
            'published_at' => ['nullable', 'date', 'after:now'],
        ];
    }

    public function messages(): array
    {
        return [
            'body.min' => 'The post body must be at least 100 characters.',
        ];
    }
}
php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string', 'min:100'],
            'category_id' => ['required', 'exists:categories,id'],
            'tags' => ['nullable', 'array'],
            'tags.*' => ['exists:tags,id'],
            'published_at' => ['nullable', 'date', 'after:now'],
        ];
    }

    public function messages(): array
    {
        return [
            'body.min' => 'The post body must be at least 100 characters.',
        ];
    }
}

Service Class Pattern

服务类模式

php
<?php

namespace App\Services;

use App\Models\User;
use App\Models\Post;
use App\Events\PostPublished;
use Illuminate\Support\Facades\DB;

class PostService
{
    public function __construct(
        private readonly NotificationService $notifications,
    ) {}

    public function publish(Post $post): Post
    {
        return DB::transaction(function () use ($post) {
            $post->update([
                'published_at' => now(),
                'status' => 'published',
            ]);

            event(new PostPublished($post));

            $this->notifications->notifyFollowers($post->author, $post);

            return $post->fresh();
        });
    }
}
php
<?php

namespace App\Services;

use App\Models\User;
use App\Models\Post;
use App\Events\PostPublished;
use Illuminate\Support\Facades\DB;

class PostService
{
    public function __construct(
        private readonly NotificationService $notifications,
    ) {}

    public function publish(Post $post): Post
    {
        return DB::transaction(function () use ($post) {
            $post->update([
                'published_at' => now(),
                'status' => 'published',
            ]);

            event(new PostPublished($post));

            $this->notifications->notifyFollowers($post->author, $post);

            return $post->fresh();
        });
    }
}

Eloquent Model

Eloquent模型

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Builder;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'slug',
        'body',
        'category_id',
        'published_at',
    ];

    protected $casts = [
        'published_at' => 'datetime',
    ];

    // Relationships
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withTimestamps();
    }

    // Scopes
    public function scopePublished(Builder $query): Builder
    {
        return $query->whereNotNull('published_at')
            ->where('published_at', '<=', now());
    }

    public function scopeByCategory(Builder $query, int $categoryId): Builder
    {
        return $query->where('category_id', $categoryId);
    }

    // Accessors & Mutators
    protected function title(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => ucfirst($value),
        );
    }
}
php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Builder;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'slug',
        'body',
        'category_id',
        'published_at',
    ];

    protected $casts = [
        'published_at' => 'datetime',
    ];

    // 关联关系
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withTimestamps();
    }

    // 查询作用域
    public function scopePublished(Builder $query): Builder
    {
        return $query->whereNotNull('published_at')
            ->where('published_at', '<=', now());
    }

    public function scopeByCategory(Builder $query, int $categoryId): Builder
    {
        return $query->where('category_id', $categoryId);
    }

    // 访问器与修改器
    protected function title(): Attribute
    {
        return Attribute::make(
            set: fn (string $value) => ucfirst($value),
        );
    }
}

Migration Best Practices

迁移最佳实践

php
<?php

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('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->foreignId('category_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('body');
            $table->timestamp('published_at')->nullable();
            $table->timestamps();

            // Indexes for common queries
            $table->index(['user_id', 'published_at']);
            $table->index('category_id');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
php
<?php

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('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->foreignId('category_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('body');
            $table->timestamp('published_at')->nullable();
            $table->timestamps();

            // 为常用查询添加索引
            $table->index(['user_id', 'published_at']);
            $table->index('category_id');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Eager Loading

预加载

php
// ❌ N+1 Problem
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name;  // Query per post
}

// ✅ Eager loading
$posts = Post::with(['author', 'category', 'tags'])->get();
foreach ($posts as $post) {
    echo $post->author->name;  // No additional queries
}

// ✅ Nested eager loading
$posts = Post::with([
    'author.profile',
    'comments.user',
    'tags',
])->get();

// ✅ Constrained eager loading
$posts = Post::with([
    'comments' => fn ($query) => $query->latest()->limit(5),
])->get();
php
// ❌ N+1 查询问题
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name;  // 每个帖子触发一次查询
}

// ✅ 预加载
$posts = Post::with(['author', 'category', 'tags'])->get();
foreach ($posts as $post) {
    echo $post->author->name;  // 无额外查询
}

// ✅ 嵌套预加载
$posts = Post::with([
    'author.profile',
    'comments.user',
    'tags',
])->get();

// ✅ 条件预加载
$posts = Post::with([
    'comments' => fn ($query) => $query->latest()->limit(5),
])->get();

How to Use

使用方法

Read individual rule files for detailed explanations and code examples:
rules/arch-service-classes.md
rules/eloquent-eager-loading.md
rules/validation-form-requests.md
rules/_sections.md
Each rule file contains:
  • YAML frontmatter with metadata (title, impact, tags)
  • Brief explanation of why it matters
  • Incorrect code example with explanation
  • Correct code example with explanation
  • Laravel 12 and PHP 8.5 specific context and references
阅读单个规则文件获取详细说明和代码示例:
rules/arch-service-classes.md
rules/eloquent-eager-loading.md
rules/validation-form-requests.md
rules/_sections.md
每个规则文件包含:
  • 带元数据的YAML前置内容(标题、影响程度、标签)
  • 规则重要性的简要说明
  • 错误代码示例及解释
  • 正确代码示例及解释
  • Laravel 12和PHP 8.5的特定上下文与参考

Full Compiled Document

完整编译文档

For the complete guide with all rules expanded:
AGENTS.md
包含所有扩展规则的完整指南:
AGENTS.md