eloquent-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Eloquent Best Practices

Eloquent 最佳实践

Query Optimization

查询优化

Always Eager Load Relationships

始终预加载关联关系

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

// ✅ Eager Loading
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name; // No additional queries
}
php
// ❌ N+1 查询问题
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // 额外执行N次查询
}

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

Select Only Needed Columns

仅选择所需列

php
// ❌ Fetches all columns
$users = User::all();

// ✅ Only needed columns
$users = User::select(['id', 'name', 'email'])->get();

// ✅ With relationships
$posts = Post::with(['user:id,name'])->select(['id', 'title', 'user_id'])->get();
php
// ❌ 获取所有列
$users = User::all();

// ✅ 仅获取所需列
$users = User::select(['id', 'name', 'email'])->get();

// ✅ 关联关系场景下
$posts = Post::with(['user:id,name'])->select(['id', 'title', 'user_id'])->get();

Use Query Scopes

使用查询作用域

php
// ✅ Define reusable query logic
class Post extends Model
{
    public function scopePublished($query)
    {
        return $query->where('status', 'published')
                    ->whereNotNull('published_at');
    }
    
    public function scopePopular($query, $threshold = 100)
    {
        return $query->where('views', '>', $threshold);
    }
}

// Usage
$posts = Post::published()->popular()->get();
php
// ✅ 定义可复用的查询逻辑
class Post extends Model
{
    public function scopePublished($query)
    {
        return $query->where('status', 'published')
                    ->whereNotNull('published_at');
    }
    
    public function scopePopular($query, $threshold = 100)
    {
        return $query->where('views', '>', $threshold);
    }
}

// 使用示例
$posts = Post::published()->popular()->get();

Relationship Best Practices

关联关系最佳实践

Define Return Types

定义返回类型

php
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
php
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

Use withCount for Counts

使用withCount高效统计

php
// ❌ Triggers additional queries
foreach ($posts as $post) {
    echo $post->comments()->count();
}

// ✅ Load counts efficiently
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}
php
// ❌ 触发额外查询
foreach ($posts as $post) {
    echo $post->comments()->count();
}

// ✅ 高效加载统计数据
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}

Mass Assignment Protection

批量赋值保护

php
class Post extends Model
{
    // ✅ Whitelist fillable attributes
    protected $fillable = ['title', 'content', 'status'];
    
    // Or blacklist guarded attributes
    protected $guarded = ['id', 'user_id'];
    
    // ❌ Never do this
    // protected $guarded = [];
}
php
class Post extends Model
{
    // ✅ 白名单可填充属性
    protected $fillable = ['title', 'content', 'status'];
    
    // 或黑名单不可填充属性
    protected $guarded = ['id', 'user_id'];
    
    // ❌ 绝对不要这样做
    // protected $guarded = [];
}

Use Casts for Type Safety

使用类型转换保证类型安全

php
class Post extends Model
{
    protected $casts = [
        'published_at' => 'datetime',
        'metadata' => 'array',
        'is_featured' => 'boolean',
        'views' => 'integer',
    ];
}
php
class Post extends Model
{
    protected $casts = [
        'published_at' => 'datetime',
        'metadata' => 'array',
        'is_featured' => 'boolean',
        'views' => 'integer',
    ];
}

Chunking for Large Datasets

大数据集分块处理

php
// ✅ Process in chunks to save memory
Post::chunk(200, function ($posts) {
    foreach ($posts as $post) {
        // Process each post
    }
});

// ✅ Or use lazy collections
Post::lazy()->each(function ($post) {
    // Process one at a time
});
php
// ✅ 分块处理以节省内存
Post::chunk(200, function ($posts) {
    foreach ($posts as $post) {
        // 处理单条数据
    }
});

// ✅ 或使用惰性集合
Post::lazy()->each(function ($post) {
    // 逐条处理
});

Database-Level Operations

数据库层面操作

php
// ❌ Slow - loads into memory first
$posts = Post::where('status', 'draft')->get();
foreach ($posts as $post) {
    $post->update(['status' => 'archived']);
}

// ✅ Fast - single query
Post::where('status', 'draft')->update(['status' => 'archived']);

// ✅ Increment/decrement
Post::where('id', $id)->increment('views');
php
// ❌ 速度慢 - 先加载到内存
$posts = Post::where('status', 'draft')->get();
foreach ($posts as $post) {
    $post->update(['status' => 'archived']);
}

// ✅ 速度快 - 单条查询
Post::where('status', 'draft')->update(['status' => 'archived']);

// ✅ 自增/自减
Post::where('id', $id)->increment('views');

Use Model Events Wisely

合理使用模型事件

php
class Post extends Model
{
    protected static function booted()
    {
        static::creating(function ($post) {
            $post->slug = Str::slug($post->title);
        });
        
        static::deleting(function ($post) {
            $post->comments()->delete();
        });
    }
}
php
class Post extends Model
{
    protected static function booted()
    {
        static::creating(function ($post) {
            $post->slug = Str::slug($post->title);
        });
        
        static::deleting(function ($post) {
            $post->comments()->delete();
        });
    }
}

Common Pitfalls to Avoid

需避免的常见陷阱

Don't Query in Loops

不要在循环中查询

php
// ❌ Bad
foreach ($userIds as $id) {
    $user = User::find($id);
}

// ✅ Good
$users = User::whereIn('id', $userIds)->get();
php
// ❌ 错误做法
foreach ($userIds as $id) {
    $user = User::find($id);
}

// ✅ 正确做法
$users = User::whereIn('id', $userIds)->get();

Don't Forget Indexes

不要忘记添加索引

php
// Migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->index();
    $table->string('slug')->unique();
    $table->string('status')->index();
    $table->timestamp('published_at')->nullable()->index();
    
    // Composite index for common queries
    $table->index(['status', 'published_at']);
});
php
// 迁移文件
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->index();
    $table->string('slug')->unique();
    $table->string('status')->index();
    $table->timestamp('published_at')->nullable()->index();
    
    // 为常用查询创建复合索引
    $table->index(['status', 'published_at']);
});

Prevent Lazy Loading in Development

开发环境禁用懒加载

php
// In AppServiceProvider boot method
Model::preventLazyLoading(!app()->isProduction());
php
// 在AppServiceProvider的boot方法中
Model::preventLazyLoading(!app()->isProduction());

Checklist

检查清单

  • Relationships eagerly loaded where needed
  • Only selecting required columns
  • Using query scopes for reusability
  • Mass assignment protection configured
  • Appropriate casts defined
  • Indexes on foreign keys and query columns
  • Using database-level operations when possible
  • Chunking for large datasets
  • Model events used appropriately
  • Lazy loading prevented in development
  • 按需预加载关联关系
  • 仅选择所需列
  • 使用查询作用域实现复用
  • 配置批量赋值保护
  • 定义合适的类型转换
  • 为外键和查询列添加索引
  • 尽可能使用数据库层面操作
  • 大数据集使用分块处理
  • 合理使用模型事件
  • 开发环境禁用懒加载