Laravel Eloquent ORM 完全指南

小飞兽 Laravel 6 次阅读 2026-07-29

Laravel Eloquent ORM 是 Laravel 框架的核心组件之一,提供优雅的 Active Record 模式来操作数据库。本文全面讲解 Eloquent 的使用方法。

模型基础

// app/Models/Article.php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Article extends Model
{
use HasFactory, SoftDeletes; // SoftDeletes 提供软删除

protected $table = "articles"; // 指定表名(默认复数形式)
protected $primaryKey = "id"; // 主键(默认 id)
protected $keyType = "int"; // 主键类型
public $incrementing = true; // 是否自增
public $timestamps = true; // 是否自动维护 created_at/updated_at
protected $dateFormat = "Y-m-d H:i:s";
const CREATED_AT = "created_at";
const UPDATED_AT = "updated_at";

protected $fillable = [ // 允许批量赋值的字段
"title", "slug", "content", "category_id", "user_id", "is_published",
];

protected $guarded = ["id"]; // 禁止批量赋值的字段
protected $casts = [ // 类型自动转换
"is_published" => "boolean",
"published_at" => "datetime",
"views" => "integer",
];

protected $with = ["author", "category"]; // 预加载(避免 N+1)
protected $appends = ["full_url"]; // 追加到 JSON 的访问器

// 软删除查询时自动排除
protected $dates = ["deleted_at"];

// 访问器
public function getFullUrlAttribute()
{
return url("article/" . $this->slug);
}

// 修改器
public function setTitleAttribute($value)
{
$this->attributes["title"] = trim($value);
$this->attributes["slug"] = \Illuminate\Support\Str::slug($value);
}

// 全局 Scope
protected static function booted()
{
static::addGlobalScope("published", function ($builder) {
$builder->where("is_published", true);
});
}
}

模型关系

// 一对一:Article 有一个 ArticleMeta
public function meta()
{
return $this->hasOne(ArticleMeta::class);
}
// 反向:ArticleMeta 属于 Article
public function article()
{
return $this->belongsTo(Article::class);
}

// 一对多:Author 有很多 Articles
public function articles()
{
return $this->hasMany(Article::class);
}
// 或指定外键
public function articles()
{
return $this->hasMany(Article::class, "user_id", "id");
}

// 多对多:Article 有很多 Tags
public function tags()
{
return $this->belongsToMany(Tag::class);
}
// 获取中间表字段
public function tags()
{
return $this->belongsToMany(Tag::class)->withPivot("created_at");
}

// 远层一对多
public function comments()
{
return $this->hasManyThrough(Comment::class, Article::class);
}

// 多态
public function comments()
{
return $this->morphMany(Comment::class, "commentable");
}

public function latestComment()
{
return $this->morphOne(Comment::class, "commentable")->latest();
}

CRUD 操作

// 创建
$article = Article::create([
"title" => "Laravel教程",
"slug" => "laravel-tutorial",
"content" => "内容...",
"user_id" => auth()->id(),
]);

// 读取
$article = Article::find(1); // 查找主键=1
$article = Article::findOrFail(1); // 不存在抛 404
$article = Article::firstWhere("slug", $slug); // 按字段查找

// 更新
$article->title = "新标题";
$article->save();

// 批量更新
Article::where("category_id", 1)->update(["is_published" => true]);

// 删除(软删除)
$article->delete();
// 强制删除
$article->forceDelete();
// 恢复
$article->restore();

// 批量删除
Article::destroy([1, 2, 3]);
Article::where("is_published", false)->delete();

查询构造器

use Illuminate\Database\Eloquent\Builder;

// 基本查询
Article::where("views", ">", 100)
->whereIn("category_id", [1, 2, 3])
->whereNotNull("published_at")
->orderBy("created_at", "desc")
->select("id", "title", "views")
->distinct()
->first();

// OR 查询
Article::where("views", ">", 1000)
->orWhere("is_featured", true)
->get();

// whereBetween / whereNotBetween
Article::whereBetween("views", [100, 1000])->get();

// 分组查询
Article::selectRaw("category_id, COUNT(*) as count, AVG(views) as avg_views")
->groupBy("category_id")
->having("count", ">", 5)
->get();

// 子查询
latestArticles = Article::select("*")
->whereNotNull("published_at")
->latest()
->limit(5);

$result = Category::select("*")
->withCount([
"articles as published_count" => function ($q) {
$q->where("is_published", true);
}
])
->get();

// 查询是否存在
if (Article::where("slug", $slug)->exists()) { ... }
if (Article::where("slug", $slug)->doesntExist()) { ... }

Scope 与访问器

// 模型中定义 Scope
class Article extends Model
{
// 本地 Scope(用法:Article::published()->latest()->get())
public function scopePublished($query)
{
return $query->where("is_published", true);
}

public function scopePopular($query, $minViews = 100)
{
return $query->where("views", ">=", $minViews);
}

public function scopeByCategory($query, $categoryId)
{
return $query->where("category_id", $categoryId);
}

// 访问器(获取属性时自动调用)
public function getExcerptAttribute($value)
{
return $value ?: \Illuminate\Support\Str::limit(strip_tags($this->content), 150);
}

public function getPublishedAtAttribute($value)
{
return $value ? \Carbon\Carbon::parse($value)->format("Y-m-d H:i") : null;
}

// 修改器(设置属性时自动调用)
public function setContentAttribute($value)
{
$this->attributes["content"] = clean($value); // 自动清理 HTML
}

// 动态追加
public function getIsHotAttribute()
{
return $this->views >= 10000;
}
}

常见问题

    • Q: with() 和 load() 有什么区别?
      A:with() 在查询构建阶段预加载关系(一次性查询);load() 在已有模型实例上延迟加载(需要时再查询)。with() 更高效,应优先使用。
    • Q: 如何避免 N+1 查询问题?
      A:在模型中定义 protected $with = ["relation"] 预加载,或在查询时使用 with("relation")。N+1 是 ORM 最常见的性能杀手。
    • Q: SoftDeletes 和普通 delete() 有什么区别?
      A:SoftDeletes 会在记录上设置 deleted_at 时间戳,查询时自动排除;forceDelete() 真正从数据库删除。恢复用 restore() 方法。
    • Q: fillable 和 guarded 怎么选?
      A:fillable 指定哪些字段可以批量赋值(更安全,推荐);guarded 指定哪些不能批量赋值。两者二选一,不要同时使用。

    延伸阅读

  • Laravel Eloquent 关系官方文档
  • Laravel 数据库性能优化:查询分析与索引设计