多态关联必须静态建表并严格遵循字段命名与关系声明约定;运行时仅自动填充commentable_id/type字段,不可动态创建或修改结构。

多态关联不能“动态创建”,只能按约定结构提前建好、按规则声明;所谓“动态”,其实是运行时根据模型实例自动填充 commentable_id 和 commentable_type 字段——字段和关系必须静态存在,否则查不到、存不进、报错是常态。
数据库迁移必须用 morphs() 或严格手写字段
别指望在运行时加字段或改字段名。Laravel 的 morphTo() 默认只认 {name}_id + {name}_type 这对组合:
- 用
$table->morphs('commentable')最省事,它自动生成commentable_id(unsignedBigInteger)、commentable_type(string)并加联合索引 - 手动写必须类型一致:
commentable_id不能是integer(Post 主键是BIGINT时会截断),commentable_type长度至少 255(类名可能很长) - 字段名不能随意改——比如叫
target_id/target_type,就得在morphTo()里显式传参:$this->morphTo('target', 'target_id', 'target_type')
morphTo() 和 morphMany() 必须配对且参数对齐
这是最常翻车的地方:写反、漏参数、名字不一致,关系就永远为空。
-
Comment.php中必须写morphTo(),不是belongsTo();方法名(如commentable)决定字段前缀 - 所有被关联模型(
Post、Video等)的morphMany()第二个参数必须和上面方法名完全一致:$this->morphMany(Comment::class, 'commentable') - 如果用了短类名(如数据库存
post而非App\Models\Post),必须在AppServiceProvider::boot()里配MorphMap::set(['post' => Post::class]),否则运行时报Class not found
保存时靠模型实例触发自动填充,不是手动赋值
别在 Comment 实例上直接设 commentable_id 或 commentable_type——Eloquent 不认这个逻辑,也不会校验。
- 正确做法:拿到
$post = Post::find(1)后,走关系方法保存:$post->comments()->save(new Comment(['body' => '...'])) - 错误做法:
Comment::create(['commentable_id' => 1, 'commentable_type' => 'App\Models\Post', ...])—— 这样写,commentable_type值可能被截断,且绕过模型事件和类型检查 - 批量插入也一样:用
$post->comments()->saveMany([...]),而不是Comment::insert([...])
查询时预加载必须带 with('relation.commentable')
只写 with('comments') 是不够的,commentable 是嵌套关系,不显式声明就不会查出来。
- 要一次拿到文章 + 所有评论 + 每条评论所属的文章/视频:
Post::with('comments.commentable')->get() - 想查所有属于某类模型的评论(比如只查文章下的评论),用
whereHas():Comment::whereHas('commentable', function ($q) { $q->instanceOf(Post::class); })->get() - 注意:没预加载就访问
$comment->commentable,会 N+1 查询——尤其列表页,性能掉得很快
真正难的不是写几行代码,而是从建表那一刻起就守住约定:字段名、类名、映射配置、关系方向,四个点只要一个松动,整个多态链就断了。调试时先看数据库里 commentable_type 存的是什么,再核对 MorphMap 和模型命名空间,比瞎猜快得多。











