在Laravel中,多态关联允许一个模型通过同一个关联方法归属于多个其他模型。例如评论(Comment)既可以属于文章(Post),也可以属于视频(Video),此时无需为每种类型建独立评论表,只需在评论表中用morph类型字段与morph id字段记录归属。创建多态关联数据的核心,是借助父模型上的关联方法完成子模型写入,让Laravel自动填充类型与id。

一、数据表与模型定义
要实现多态关联,首先需要在子模型对应的数据表中加入两个关键字段:用于记录关联模型类型的字符串字段(通常叫commentable_type)和用于记录关联模型主键的字段(通常叫commentable_id)。这两个字段组合,就能唯一定位到某一条父记录。
下面以评论系统为例,先定义迁移文件。注意字段命名需与后续模型中的morph方法保持一致,Laravel默认使用“关联方法名加_type和_id”的约定。
<p>模型层需要双向定义关系。在 Comment 模型中使用 morphTo 声明自己可被多态归属,在 Post 与 Video 模型中使用 morphMany 声明拥有多种评论。这样 Laravel 才能根据方法名推导出上面迁移中的 commentable 前缀字段。</p>
<pre class=brush:php;toolbar:false>
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Comment extends Model
{
protected $fillable = ['body'];
// 声明多态归属,方法名 commentable 决定字段前缀
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
// 文章拥有多条评论,第二个参数为关联方法名
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
二、使用关联方法创建数据
最推荐的做法是拿到父模型实例后,调用其关联方法返回的关联对象来写入子模型。Laravel 会自动将父模型类型与 id 填入子表的 commentable_type 和 commentable_id,开发者完全不必手动赋值,从而避免类型字符串写错。
以下代码演示给某篇文章添加评论。先查询出 Post,再通过 comments 关联使用 create 方法。create 内部会基于当前关联自动补全多态字段,返回的 Comment 实例也已携带正确关系。
<p>如果换成视频,只需要把 Post 换成 Video,其余代码完全一致。这种写法让业务层不用关心到底关联的是哪类模型,极大降低耦合。</p>
<p>需注意,若直接写 <code>Comment::create(['body' => 'x', 'commentable_type' => Post::class, 'commentable_id' => 1])</code> 虽然可行,但类型类名容易拼错,且失去关联语义。生产环境建议统一走父模型关联写入。</p>
<h2>三、在控制器中接收请求并写入</h2>
<p>实际接口中,前端往往传回父类型与父 id,再由后端判断并写评论。此时可借助 morphMap 给类型起别名,避免把完整类名暴露给客户端,也方便后期模型改名。</p>
<p>在 AppServiceProvider 的 boot 中注册别名后,前端传 type=post 即可,Laravel 会自动映射回模型。下面示例展示如何安全地创建多态数据。</p>
<pre class=brush:php;toolbar:false>
<?php
namespace AppProviders;
use IlluminateSupportFacadesRoute;
use IlluminateDatabaseEloquentRelationsRelation;
use IlluminateSupportServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// 注册多态别名,键为别名,值为模型类
Relation::morphMap([
'post' => AppModelsPost::class,
'video' => AppModelsVideo::class,
]);
}
}
控制器里根据别名用 findOrFail 拿到父模型,再复用前面的关联写入逻辑。这样即使模型命名空间调整,只要别名不变,前端无需改动。
四、常见错误与排查
新手常犯的错误是手动填充 commentable_type 时写了小写表名或错误命名空间,导致后续 morphTo 查询返回 null。另一个坑是在迁移中把 commentable_id 设成普通 integer 却关联了 uuid 主键模型,引发类型不匹配。
排查时可用
$comment->commentable_type打印确认是否为完整类名或已注册的 morphMap 别名。若使用别名,确保 morphMap 在查询前已 boot。此外,使用$comment->commentable懒加载关系时,若返回为空,优先检查两字段是否同时正确写入,而不是怀疑关联定义。
| 写法 | 优点 | 风险 |
|---|---|---|
| 父模型关联 create | 自动填类型与id,语义清晰 | 需先取父模型实例 |
| 手动赋值 Comment::create | 不依赖父实例 | 类型字段易错,难维护 |
| save 到关联 | 可先构对象再存 | 同样要求关联上下文 |
五、总结
Laravel 多态关联创建数据的要点,在于利用父模型上的 morphMany 关联对象完成子模型持久化。无论 create 还是 save,框架都会接管类型与 id 的写入。配合 morphMap 既能隐藏实现细节,也能让接口更稳健。
只要遵循“先有父实例,再通过关联写子模型”的原则,就能避开绝大多数多态关联写入问题,把一套评论逻辑平滑复用到任意业务模型上。