eloquent 是 laravel 数据库操作默认语言,需正确定义关联、区分 where/wherehas 逻辑、理解 save/update 差异及 toarray/jsonserialize 行为差异。

Eloquent 不是 ORM 封装层,它是 Laravel 应用里数据库操作的默认语言——用错姿势,查得慢、改得崩、关联漏数据。
怎么定义一个能正确加载关联的模型
很多人写完 User 模型就直接 User::with('posts'),结果 N+1 还在发生。根本原因是没声明关联方法,或用了错误的返回类型。
- 必须用
public function posts()这种命名方法(不能叫getPosts()),且返回值必须是HasMany/BelongsTo等关系构造器,不能是数组或集合 - 外键名默认按约定:比如
posts.user_id,如果实际字段是author_id,得显式传参:return $this->hasMany(Post::class, 'author_id'); - 反向关联(如
Post找User)别漏掉foreignKey参数,否则会查user_id字段,而你表里可能是created_by
示例:
class Post extends Model
{
public function author()
{
return $this->belongsTo(User::class, 'created_by');
}
}
where() 和 whereHas() 混用时为什么查不到数据
whereHas() 是进关联表过滤,where() 是查主表——顺序和嵌套逻辑一错,条件就失效。最常见的是想“查有未删除评论的文章”,却写成 Article::where('status', 'published')->whereHas('comments', fn ($q) => $q->where('deleted_at', null)),但忘了 comments 表本身可能有软删除。
-
whereHas()默认不包含软删除记录;要查含软删除的关联,得加withTrashed():whereHas('comments', fn ($q) => $q->withTrashed()->where('deleted_at', null)) - 多个
whereHas()之间是 AND 关系,如果想 OR,得用whereDoesntHave()或子查询 - 不要在
whereHas()里调用select(),Eloquent 会忽略它;真要限制字段,得用with(['comments:id,title'])配合select()主表字段
save() 和 update() 看似一样,其实触发逻辑完全不同
$user->name = 'new'; $user->save(); 会触发模型事件(saving、saved)、检查 $fillable、走访问器/修改器;而 User::where('id', 1)->update(['name' => 'new']) 是直接 SQL 更新,绕过所有模型生命周期。
- 批量更新一律用
update(),但别指望它触发updated事件——要事件就得逐条save() -
save()对新模型是INSERT,对已有模型是UPDATE;但如果你手动设了$model->exists = false,它又会强行插一条 - 用
update()时,如果字段不在$fillable里,不会报错,只是默默忽略——这是线上静默丢数据的高发点
toArray() 和 jsonSerialize() 返回内容为什么不一样
toArray() 只处理模型属性和显式加载的关联(load() 或 with() 过的),而 jsonSerialize() 在 API 响应时被自动调用,会额外展开隐藏字段($hidden)、追加字段($appends)、格式化日期($casts 中的 date 类型)。
- 调试时用
dd($model->toArray())看不到$appends的字段,但response()->json($model)里会有 - 如果重写了
jsonSerialize(),记得调用parent::jsonSerialize(),否则$appends和日期格式全失效 -
toArray()不会递归调用关联模型的toArray(),除非那个关联已经被加载;未加载的关联在toArray()里是空数组,不是null
关联加载时机、事件触发边界、序列化层级——这些地方没有报错,但数据就是不对。比语法错误更难 debug 的,是 Eloquent 默认行为和你直觉之间的那几毫秒偏差。









