一、适合放在模型中的方法

Laravel 采用 ActiveRecord 风格,模型本身就是"数据 + 围绕该数据的操作"的载体。以下几类方法适合放模型:

  1. 关联关系belongsTomorphTohasMany 等。如 UserFav::userfavable()UserFav::user()
  2. 访问器/修改器getIsFavAttributesetTypeAttribute 等单字段派生逻辑。
  3. 查询作用域(Scope):对自身查询的复用约束,如 scopeOfUser($query, $userId)
  4. 简单查询封装:直接查本表、返回本模型数据的静态方法,如 UserFav::cachedFavIds()
  5. 常量/映射表:如 TikuQuestion::$typeMap
  6. 单记录的业务判断:如 $order->isPaid()$user->isAdmin(),只依赖当前实例字段。

判断准则:逻辑只围绕"这一张表的数据"展开,没有外部副作用、没有跨模型编排。

二、适合放在服务类(Service)中的方法

当逻辑出现以下特征时,应抽到 Service:

  1. 跨模型编排:一个操作要写多张表、调多个模型,如"下单"需扣库存 + 生成订单 + 记日志。
  2. 涉及外部副作用:发邮件、推送通知、调用第三方 API、发队列任务。
  3. 事务边界:需要 DB::transaction 包裹多步操作。
  4. 依赖注入多个协作对象:如同时用到 Repository、Cache、Config、HttpClient。
  5. 领域规则较重:如价格计算、权限策略、风控规则,逻辑复杂且需独立测试。
  6. 同一逻辑被多个入口复用:控制器、命令、Job 都要调,放模型会让模型承担过多职责。

判断准则:逻辑"使用"模型,而不是"属于"模型。

三、cachedFavIds 适合放在模型中吗?

结论:当前实现放在模型里是合适的。 理由:

  1. 它本质是一个"带缓存的查询封装",查询的是 UserFav 自身表,返回的是本模型字段(userfavable_id),属于模型的"简单查询封装"范畴,与上面第 4 类吻合。
  1. Laravel 惯用法支持。Laravel 官方文档和很多大型项目(如 OctoberCMS、Botble)都把 Cache::remember 直接写在模型静态方法或作用域里。Eloquent 本身也内置了 Model::remember() 这类缓存辅助。
  1. 无跨模型编排、无外部副作用。它只读自身数据 + 操作缓存,没有调其他模型、没有发事件、没有事务,不构成"服务"该承担的职责。
  1. 静态方法 + 自包含,调用方(TikuQuestionResourceUserFavController)只需 UserFav::cachedFavIds(...) 即可,依赖清晰。

不过有 两个信号出现时,建议迁移到 Service

信号 说明
缓存键/TTL 策略开始膨胀 出现多级缓存、缓存标签、批量预热、失效策略复杂化
缓存逻辑被多个模型复用 例如 TikuQuestionArticle 都需要"用户是否收藏"的通用能力

此时可以抽一个 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 不迟——架构应随复杂度演进,不必过早抽象