在 Yii 应用中,应将用户查询逻辑封装为独立的 UserQueryService 查询服务类(继承 yii\base\Component 而非 ActiveRecord),通过 DI 容器注册并支持构造函数或属性注入,在控制器中调用 findActiveUsers 等方法获取 ActiveDataProvider 供 GridView 使用。

在 Yii 应用中,当多个控制器或服务类需要复用同一套用户查询逻辑(如按状态筛选活跃用户、带关联统计的分页列表),又不想把 Model::find()->with() 这类硬编码查询散落在各处,就必须把查询行为从 Controller 或 Service 中剥离出来,交由独立的服务类统一管理并自动注入。
定义查询服务类
创建 common/services/UserQueryService.php,继承自 yii\base\Component,不继承 ActiveRecord —— 它只负责组装查询,不映射数据:
这一步不能写成 ActiveRecord 子类,否则会干扰 AR 的生命周期钩子和属性赋值逻辑。
public function findActiveUsers($page = 1, $limit = 20) {
$query = User::find()
->where(['status' => User::STATUS_ACTIVE])
->orderBy(['created_at' => SORT_DESC]);
return new ActiveDataProvider([
'query' => $query,
'pagination' => ['pageSize' => $limit, 'page' => $page - 1],
]);
}
注册到 DI 容器
在 config/web.php 的 'container' 配置项中添加定义:
必须使用 【class】 键而非 【__class】,否则 Yii2.0.43+ 版本会忽略该注册,导致 get() 返回 null。
'definitions' => [
common\services\UserQueryService::class => [
'class' => common\services\UserQueryService::class,
],
],
在控制器中自动注入
方法一:构造函数注入(推荐)
第一步:修改控制器继承关系,让其扩展自自定义的 BaseController(该基类已预设 DI 支持);
第二步:在控制器构造函数签名中声明类型提示:
public function __construct($id, $module, UserQueryService $userQueryService, $config = []) {
第三步:保存实例并调用父构造:
$this->userQueryService = $userQueryService;
parent::__construct($id, $module, $config);
注意:参数顺序必须严格为 $id, $module, $service, $config,错一位就会触发框架初始化失败。
方法二:属性注入(适用于已有控制器无法改构造函数时)
在控制器类顶部添加 public 属性:
public $userQueryService;
在 config/web.php 中追加配置:
'singletons' => [
common\controllers\UserController::class => function ($container) {
$controller = new common\controllers\UserController('user', Yii::$app->getModule('user'));
$controller->userQueryService = $container->get(UserQueryService::class);
return $controller;
},
],
调用查询服务
在 action 中直接使用:
$dataProvider = $this->userQueryService->findActiveUsers(1, 15);
这一步操作起来很简单,直接把 dataProvider 传给 GridView 就能渲染出带分页的活跃用户列表。


















