必须建立独立repository层以剥离数据库逻辑:定义接口约束方法签名并返回基础类型,实现类继承component封装查询、禁止暴露activequery,service通过di注入接口调用且不执行写操作,缓存需含业务上下文键。

在 Yii 应用中需要将数据库访问逻辑从控制器或服务层彻底剥离,避免模型类承担过多职责、防止 SQL 泄露到业务代码中,同时支持单元测试与多数据源切换时,必须建立独立的 Repository 层。
定义 Repository 接口
在 app/repositories 下创建 UserRepositoryInterface.php:
这一步不是可选的——接口先行才能保证后续实现可替换、可 Mock。没有接口约束,Repository 就退化为普通工具类。
【必须声明方法签名,不带具体实现】例如:public function findById(int $id): ?User; 和 public function findByEmail(string $email): ?User;。
接口中禁止出现 ActiveRecord 或 Query 类型提示,只允许返回 Model 实例或 null、array、int 等基础类型。
实现 UserRepository 类
在相同目录下新建 UserRepository.php,实现接口:
继承 yii\base\Component,而非 ActiveRecord——它不映射表,只封装查询逻辑。
构造函数注入模型类名(如 User::class)或直接依赖具体模型实例,避免硬编码;若需支持多库,此处应接收 \yii\db\Connection 实例。
所有查询方法内部调用 User::find() 构建 ActiveQuery,但绝不暴露该对象给调用方;one() / all() 返回后立即转为纯数组或模型实例,不返回 ActiveQuery 对象本身。
在 Service 中使用 Repository
第一步:在 Service 构造函数中接收 UserRepositoryInterface 实例,通过 DI 容器自动注入;
第二步:调用 $this->userRepository->findById($id) 获取用户,不再写 User::findOne($id);
第三步:对返回值做业务判断,例如 if (!$user) throw new NotFoundException('User not found');;
第四步:将处理结果(如用户头像 URL、权限列表)组装为 DTO 或数组返回,不传递 AR 实例出 Service 层。
注意:Service 中不得调用 save()、delete() 等写操作——这些应封装进单独的 UserCommand 或由 Repository 提供 saveUser(User $user) 方法(该方法内部才调用 $user->save())。
为 Repository 添加缓存支持
方法一:在 Repository 实现类中注入 cache 组件,对 findById 等高频读方法加缓存键:
$key = sprintf('user:%d', $id);
$user = $this->cache->get($key);
若未命中,则执行查询并写入:$this->cache->set($key, $user, 3600);
方法二:用装饰器模式包装 Repository,不修改原实现——适合已有项目渐进改造。
【缓存键必须包含业务上下文,如租户 ID 或状态标记】例如多租户系统中,键应为 s123:user:456,否则不同租户可能读到错误数据。











