点赞和收藏必须用多态关联,因为需统一处理post、comment、video等多种模型的交互行为;通过likes表的likeable_type和likeable_id字段动态关联,配合联合唯一索引防止重复,并在模型中正确定义morphto/morphmany关系。

点赞和收藏为什么必须用多态关联
因为点赞(like)和收藏(favorite)行为可能发生在多种模型上——比如 Post、Comment、Video,而你不可能为每种组合都建一张表(post_likes、comment_likes…)。laravel 的多态关联用一张 likes 表就能统一处理,靠 likeable_type 和 likeable_id 两个字段动态指向任意模型。
如何定义 Like 和 Favorite 模型及迁移
别拆成两张表,统一用 interactions 表更灵活(或按业务拆成 likes 和 favorites,但结构一致)。以 likes 为例:
php artisan make:migration create_likes_table
迁移里定义:
-
user_id:谁点的 -
likeable_type:字符串,如AppModelsPost -
likeable_id:对应模型主键值 - 联合唯一索引:
(user_id, likeable_type, likeable_id),防止重复点赞
模型 Like 中定义多态关系:
public function likeable() { return $this->morphTo(); }
在 Post/Comment 等被操作模型中声明多态关系
比如 Post 模型要支持被点赞,就在类里加:
public function likes() { return $this->morphMany(Like::class, 'likeable'); }
同理,Comment、Video 都加这一行。注意参数顺序:morphMany(模型类, 多态字段前缀),这里前缀是 likeable,所以数据库字段是 likeable_type 和 likeable_id。
常见错误:
- 把
likeable写成like或liked,导致字段名不匹配 - 迁移里漏掉
likeable_type的index,查某篇文章所有点赞时性能暴跌 - 没设联合唯一索引,用户手抖连点两次就存了两条重复记录
实际使用:点赞/取消点赞的逻辑怎么写
核心是先查是否存在,再决定 insert 或 delete。别用 updateOrCreate —— 它无法安全处理多态字段的复合条件。
推荐写法:
$like = $post->likes()->where('user_id', auth()->id())->first(); if ($like) { $like->delete(); } else { $post->likes()->create(['user_id' => auth()->id()]); }
更简洁可复用的方式是封装到 trait 或 service 中,但关键点就两个:
- 必须用
where显式查user_id+ 多态模型 ID,不能只依赖auth()->id()就 create - 前端触发时,接口返回当前状态(已点赞 / 未点赞),避免客户端靠 toggle 按钮状态出错
多态本身不难,难的是在高并发场景下保证“点一下只生效一次”,这得靠数据库唯一索引兜底,而不是靠 PHP 层判断。