在 laravel 5.5 中,user 模型用 hasmany(post::class) 建立一对多关系,“多”方 post 模型用 belongsto(user::class),外键非约定名需显式传参,迁移应加外键约束防脏数据,关联查询须用 with() 预加载避免 n+1。

直接说结论:在 Laravel 5.5 中,hasMany() 和 belongsTo() 是建立一对多关系的两个核心方法,分别写在“一”方和“多”方模型里;默认约定外键名是 xxx_id(小写下划线),主键是 id,不显式传参也能跑通——但一旦字段名或表结构偏离约定,就必须手动指定参数。
怎么在 User 和 Post 模型里写关联方法
一对多关系中,“一”方(User)用 hasMany(),“多”方(Post)用 belongsTo()。这是固定搭配,不能反着来。
-
User模型里加:public function posts() { return $this->hasMany(Post::class); } -
Post模型里加:public function user() { return $this->belongsTo(User::class); }
注意:方法名(如 posts、user)只是调用时的动态属性名,可自定义;但返回值必须是对应关系方法,否则关联查询会失败。
外键字段名不是 user_id 怎么办
如果 posts 表里外键叫 author_id 而不是默认的 user_id,就必须显式传参,否则 $user->posts 查不到数据。
-
hasMany()第二个参数是外键名:$this->hasMany(Post::class, 'author_id') -
belongsTo()第二个参数也是外键名:$this->belongsTo(User::class, 'author_id') - 如果
users表主键不是id(比如叫uid),还得补第三个参数:$this->belongsTo(User::class, 'author_id', 'uid')
漏掉这个参数,Eloquent 会默默按 user_id 去查,SQL 日志里能看到 WHERE posts.user_id = ?,但表里根本没有这列——查出来永远空数组,还不报错。
迁移文件里没加外键约束会怎样
数据库层面没建外键(比如没写 $table->foreign('user_id')->references('id')->on('users')),Eloquent 的关联查询照常工作,但会失去两层保障:
- 数据库不会阻止你插入一个不存在的
user_id,导致脏数据 - 执行
php artisan migrate:fresh或手动删表重建时,如果顺序错(先删users再删posts),可能因外键依赖失败而中断
所以哪怕只是本地开发,也建议在迁移里加上外键定义,并配好 onDelete('cascade') 或 onDelete('set null'),避免后期上线踩坑。
关联查询时 N+1 问题怎么破
写 User::all()->each(fn($u) => $u->posts->count()) 这种代码,会触发 N+1 查询:1 次查用户,N 次查每个用户的 posts。Laravel 5.5 已支持 with() 预加载。
- 正确写法:
User::with('posts')->get(),只发 2 条 SQL - 如果还要过滤 posts(比如只查已发布的),不能写
with('posts')->where('posts.published_at', '!=', null)—— 这会报错,得用闭包约束:with(['posts' => fn($q) => $q->whereNotNull('published_at')])
这个闭包语法在 Laravel 5.5 是有效的,但容易被当成高阶用法忽略;其实它是解决“带条件预加载”的最简方式,比手写 join 更安全、更 Eloquent。
真正容易被忽略的是:hasMany() 返回的是 HasMany 实例(不是集合),只有调用后才执行查询;而 belongsTo() 默认是延迟加载,但如果你在循环里反复访问 $post->user->name 却没预加载,照样 N+1。别只盯着“一”方,要双向检查。











