
本文介绍如何在 yii2 框架中通过构造函数依赖注入(constructor injection)将服务类(如 userservice)自动注入到控制器中,替代手动实例化,提升代码可测试性与可维护性,并支持跨多个控制器统一管理依赖。
本文介绍如何在 yii2 框架中通过构造函数依赖注入(constructor injection)将服务类(如 userservice)自动注入到控制器中,替代手动实例化,提升代码可测试性与可维护性,并支持跨多个控制器统一管理依赖。
在 Yii2 中,默认控制器(yii\web\Controller)不支持构造函数参数自动注入,因其由框架内部通过 new $class() 实例化,绕过了 DI 容器。若直接在控制器构造函数中声明服务类型参数(如 UserService $userService),会因参数缺失导致 PHP 致命错误。因此,需采用“父控制器 + 依赖注入配置”的组合方案实现优雅解耦。
✅ 推荐方案:自定义 BaseController 并配置容器
首先,创建一个继承自 yii\web\Controller 的基类 BaseController,并在其构造函数中接收并保存所需服务:
// common/controllers/BaseController.php
namespace common\controllers;
use yii\web\Controller;
use common\services\UserService;
class BaseController extends Controller
{
protected UserService $userService;
public function __construct($id, $module, UserService $userService, $config = [])
{
$this->userService = $userService;
parent::__construct($id, $module, $config);
}
}
⚠️ 注意:BaseController 必须显式调用 parent::__construct(),且参数顺序需与 yii\web\Controller::__construct() 严格一致($id, $module, ..., $config),否则框架初始化失败。
接着,在应用配置(如 config/web.php 或 config/main.php)中,告知 DI 容器:所有 BaseController 实例应通过容器解析,并自动注入 UserService:
// config/web.php
'container' => [
'definitions' => [
common\controllers\BaseController::class => [
'__class' => common\controllers\BaseController::class,
'__construct()' => ['{id}', '{module}', ['class' => common\services\UserService::class]],
],
],
],
但更简洁、推荐的做法是——利用 Yii2 容器的自动构造函数注入能力(需确保服务已注册),无需显式定义 BaseController 的完整构造参数。只需保证 UserService 已被容器知晓(例如已定义为单例或默认类):
// config/web.php
'container' => [
'singletons' => [
common\services\UserService::class => common\services\UserService::class,
// 或带配置的实例:
// common\services\UserService::class => [
// 'class' => common\services\UserService::class,
// 'apiClient' => ['class' => common\components\ApiClient::class],
// ],
],
],
此时,只要 BaseController 构造函数签名符合容器解析规则(参数类型明确、有对应注册项),Yii 在创建控制器实例时便会自动完成注入。
最后,让业务控制器继承 BaseController:
// controllers/SiteController.php
namespace app\controllers;
use common\controllers\BaseController;
class SiteController extends BaseController
{
public function actionLogin()
{
// ✅ 自动注入,无需 new UserService()
return $this->userService->login();
}
}
? 扩展性说明
- 若需注入多个服务(如 OrderService、NotificationService),只需在 BaseController 构造函数中追加参数并赋值即可,容器会按类型自动解析;
- 对于仅在个别控制器中使用的服务,也可采用 Action 级注入(通过 Yii::$container->get() 或 Action::init() 中注入),但不如构造函数注入清晰、可测;
- 避免在控制器中使用 Yii::$app->get('userService') 这类服务定位器模式,它隐藏了依赖关系,违背控制反转原则。
✅ 总结
通过定义 BaseController 统一接收服务依赖,并配合 Yii2 DI 容器的单例注册与自动构造注入机制,可彻底取代 new UserService() 的硬编码方式。该方案具备高内聚、低耦合、易单元测试等优势,是 Yii2 应用工程化开发的标准实践之一。











