一、适合放在模型中的方法
Laravel 采用 ActiveRecord 风格,模型本身就是"数据 + 围绕该数据的操作"的载体。以下几类方法适合放模型:
- 关联关系:
belongsTo、morphTo、hasMany等。如UserFav::userfavable()、UserFav::user()。 - 访问器/修改器:
getIsFavAttribute、setTypeAttribute等单字段派生逻辑。 - 查询作用域(Scope):对自身查询的复用约束,如
scopeOfUser($query, $userId)。 - 简单查询封装:直接查本表、返回本模型数据的静态方法,如
UserFav::cachedFavIds()。 - 常量/映射表:如
TikuQuestion::$typeMap。 - 单记录的业务判断:如
$order->isPaid()、$user->isAdmin(),只依赖当前实例字段。
判断准则:逻辑只围绕"这一张表的数据"展开,没有外部副作用、没有跨模型编排。
二、适合放在服务类(Service)中的方法
当逻辑出现以下特征时,应抽到 Service:
- 跨模型编排:一个操作要写多张表、调多个模型,如"下单"需扣库存 + 生成订单 + 记日志。
- 涉及外部副作用:发邮件、推送通知、调用第三方 API、发队列任务。
- 事务边界:需要
DB::transaction包裹多步操作。 - 依赖注入多个协作对象:如同时用到 Repository、Cache、Config、HttpClient。
- 领域规则较重:如价格计算、权限策略、风控规则,逻辑复杂且需独立测试。
- 同一逻辑被多个入口复用:控制器、命令、Job 都要调,放模型会让模型承担过多职责。
判断准则:逻辑"使用"模型,而不是"属于"模型。
三、cachedFavIds 适合放在模型中吗?
结论:当前实现放在模型里是合适的。 理由:
- 它本质是一个"带缓存的查询封装",查询的是
UserFav自身表,返回的是本模型字段(userfavable_id),属于模型的"简单查询封装"范畴,与上面第 4 类吻合。
- Laravel 惯用法支持。Laravel 官方文档和很多大型项目(如 OctoberCMS、Botble)都把
Cache::remember直接写在模型静态方法或作用域里。Eloquent 本身也内置了Model::remember()这类缓存辅助。
- 无跨模型编排、无外部副作用。它只读自身数据 + 操作缓存,没有调其他模型、没有发事件、没有事务,不构成"服务"该承担的职责。
- 静态方法 + 自包含,调用方(
TikuQuestionResource、UserFavController)只需UserFav::cachedFavIds(...)即可,依赖清晰。
不过有 两个信号出现时,建议迁移到 Service:
| 信号 | 说明 |
|---|---|
| 缓存键/TTL 策略开始膨胀 | 出现多级缓存、缓存标签、批量预热、失效策略复杂化 |
| 缓存逻辑被多个模型复用 | 例如 TikuQuestion、Article 都需要"用户是否收藏"的通用能力 |
此时可以抽一个 UserFavService 或更通用的 FavoriteService,把缓存策略、失效逻辑收敛进去,模型只保留关联定义和基础查询。例如:
// Modules/Ucenter/app/Services/UserFavService.php
class UserFavService
{
public function favoritedIds(int $userId, string $morphType): array { /* ... */ }
public function refresh(int $userId, string $morphType): void { /* ... */ }
public function isFavorited(int $userId, string $morphType, int $targetId): bool { /* ... */ }
}
小结
| 维度 | 模型 | Service |
|---|---|---|
| 数据归属 | 本表数据 | 跨表/跨模型 |
| 副作用 | 只读或单表写 | 事务、事件、外部调用 |
| 复杂度 | 简单查询/判断 | 编排、规则计算 |
| 依赖 | 自包含 | 需注入多个协作对象 |
| Laravel 惯例 | ActiveRecord 胖模型 | 仅在编排复杂时引入 |
当前 UserFav::cachedFavIds() 和 forgetFavCache() 属于"带缓存的查询封装",放模型是符合 Laravel 风格的轻量方案;等缓存策略变复杂或多模型复用时,再抽 Service 不迟——架构应随复杂度演进,不必过早抽象。