模型关联需严丝合缝:数据库外键、模型定义、调用方式三者一致;一对一、一对多、多对多各有规范;预加载with()是刚需,避免n+1查询。

模型关联不是“写完就跑”,而是要从数据库结构、模型定义、调用方式三处严丝合缝。写错方向、漏传参数、不预加载,这三类问题占了实际开发中 90% 的关联失效案例。
一对一关系:外键在哪,belongsTo 就写在哪
典型场景如 User ↔ Profile(用户与资料)。Profile 表必须含 user_id 字段——这个字段就是外键,它决定了「多」方是谁。
- User 模型里写 hasOne:
return $this->hasOne(Profile::class, 'user_id', 'id'); - Profile 模型里写 belongsTo:
return $this->belongsTo(User::class, 'user_id', 'id'); - 若主键不是 id(比如用 uuid),两边第三个参数都得改成
'uuid' - 若外键叫
owner_id而非user_id,两个方法的第二个参数必须同步改为'owner_id'
一对多关系:hasMany 只能写在“一”方,否则永远查不到
例如 User 有多个 Post,Post 表里有 user_id 字段。那 hasMany 就只能放在 User 模型;反过来说,Post 模型里必须用 belongsTo 关联回 User。
- User 模型:
public function posts() { return $this->hasMany(Post::class, 'user_id', 'id'); } - Post 模型:
public function user() { return $this->belongsTo(User::class, 'user_id', 'id'); } - 别在 Post 模型里写
hasMany——Eloquent 会去查post_id字段,根本不存在 - 插入时用
$user->posts()->create([...])会自动填user_id;但用new Post([...])->save()必须手动赋值user_id
多对多关系:中间表字段要显式声明才能拿到
比如 User ↔ Role,靠中间表 role_user 连接。默认只返回 Role 模型,中间表里的 created_at、is_primary 等字段不会自动带出。
- User 模型:
return $this->belongsToMany(Role::class)->withPivot('is_primary', 'created_at'); - 查询后访问
$user->roles->first()->pivot->is_primary才能取到中间表字段 - 若中间表名不是 Laravel 默认的
role_user(比如叫user_roles),需完整指定:belongsToMany(Role::class, 'user_roles', 'user_id', 'role_id') - 用
whereHas或withPivot前,确保中间表字段加了数据库索引,否则性能骤降
预加载是刚需,不是可选项
不加 with() 就直接访问 $user->posts,每访问一次就发一条 SQL——100 个用户循环输出文章数,就是 101 次查询。
- 查单个用户及文章:
User::with('posts')->find(1),不是User::find(1)->posts - 查多个用户并遍历:
User::with('posts')->get(),不是User::all()后再 foreach - 只取最新 3 篇?不能只靠
with(),要用子查询或先withCount()再手动筛选 - 加 where 条件(如只查已发布):
with(['posts' => fn($q) => $q->where('published', 1)])











