应将Yii模型中的查询逻辑剥离到独立服务层,避免控制器和模型过度耦合;直接在控制器写find()->where()会导致业务规则散落、难以维护和修改;需用查询服务类(如ProductQueryService)封装可复用逻辑,并通过Repository模式处理复杂查询与多数据源适配。

将Yii模型中的查询逻辑剥离到独立服务层,避免控制器和模型过度耦合,让业务规则可复用、可测试、可追踪。
为什么不能直接在控制器里写find()->where()?
ActiveRecord查询若散落在多个控制器动作中,一旦商品下架规则变更(比如新增“审核状态=已通过”条件),就得全局搜索所有where语句逐个修改——漏改一处就导致未审核商品出现在首页。模型本身只应定义数据结构与基础验证,不承载业务判定逻辑。
这一步必须做:把“哪些商品该展示”这种带业务语义的判断,从Controller或View里彻底移出。
创建查询服务类并注入依赖
在common/services目录下新建ProductQueryService.php,继承yii\base\Component:
class ProductQueryService extends Component { public function findAvailableProducts($category_id = null) { $query = Product::find()->where(['status' => Product::STATUS_ONLINE]); if ($category_id) { $query->andWhere(['category_id' => $category_id]); } return $query->asArray()->all(); } }
在common/config/main.php的components中注册该服务:【'productQuery' => ['class' => 'common\services\ProductQueryService']】
注册后即可在任意控制器中调用\Yii::$app->productQuery->findAvailableProducts(123),无需重复new实例或传参连接对象。
用Repository模式封装更复杂的查询边界
当查询涉及多表关联、缓存策略、权限过滤时,直接扩展ActiveRecord会污染模型职责。此时应引入Repository抽象:
第一步:在common/repositories下创建ProductRepository.php,声明接口契约:
interface ProductRepositoryInterface { public function findHotProductsByRegion(string $region_code, int $limit = 10): array; }
第二步:实现类MysqlProductRepository继承该接口,内部使用Yii::$app->db->slave走从库,并自动添加WHERE deleted_at IS NULL软删除过滤 —— 这个条件若漏写,就会查出已被逻辑删除的商品。
第三步:在容器配置中绑定接口与实现:'container' => ['definitions' => [ProductRepositoryInterface::class => MysqlProductRepository::class]]。后续替换为Redis缓存实现时,只需改这一行,控制器代码零改动。
禁止在服务层调用$this->render()或触发视图渲染
服务层只负责数据获取与组装,绝不处理HTTP响应头、布局或模板渲染。若在ProductQueryService里误写$this->render('list', [...]),会导致JSON API返回混入HTML片段,前端解析失败。
控制器中必须显式控制输出:return $this->asJson(['data' => \Yii::$app->productQuery->findAvailableProducts()]);











