yii依赖注入分yii 2和yii 3两套不兼容机制:yii 3必须在顶层di配置块中声明绑定,set()单独调用无效;yii 2需在components或启动前调用set(),且注意执行时机早于首次get()。

Yii 的依赖注入容器不是“配了就能用”的黑盒,它分 Yii 2 和 Yii 3 两套完全不兼容的机制;用错版本、写错位置、混淆配置层级,90% 的“注入失败”都卡在这三步上。
Yii 3 必须在 di 配置块里注册,set() 单独调用无效
Yii 3 彻底弃用全局 Yii::$container,所有绑定必须声明在应用顶层的 di 配置中,和 params、services 并列。你在 config/web.php 里写 Yii::getContainer()->set('FooInterface', 'FooImpl') 完全没用——容器根本没加载这行代码。
-
di是顶层键,不能嵌套在container或components下 - 接口绑定必须用数组语法:
'app\interfaces\FooInterface' => ['class' => 'app\services\FooImpl'] - 如果绑定的是具体类(非接口),可简写为字符串:
'app\services\FooService' => 'app\services\FooService' - 漏掉
di块或写错路径,运行时抛出No entry or class found for 'xxx'
Yii 2 的 set() 要在 components 或启动时机调用
Yii 2 中 Yii::$container->set() 是有效的,但必须确保执行时机早于首次 get()。最稳妥的位置是应用配置的 components 数组里初始化容器,或在 index.php 启动前调用。
- 在
config/web.php的components外层直接写Yii::$container->set(...)可能因加载顺序导致失效 - 推荐统一在
components内注册:用'class' => 'app\services\Notifier'+'transport' => ['class' => 'app\mailers\SmtpMailer']实现 setter 注入 - 构造注入依赖类型提示,如
public function __construct(LoggerInterface $logger),容器会自动解析并注入已注册的实现 - 别名绑定后,
new Foo($container->get('logger'))这种手动传参方式会绕过容器生命周期管理,失去单例保障
setter 注入只响应显式配置项,不扫描方法或注解
Yii 不会自动发现 setApiUrl() 并注入值,它只在你明确提供同名键时才调用对应 setter。@property PHPDoc 注解纯属 IDE 提示,运行时不读取、不解析。
- 配置项
'apiUrl' => 'https://api.example.com'→ 触发setApiUrl()(注意大小写:setXxx对应xxx) - 配置项
'timeout' => 5若无setTimeout(),则直接赋值给 public 属性$timeout(不推荐,破坏封装) - setter 方法必须是
public,且参数类型不能太激进(如string|null接收空字符串可能报错) - 想靠注解驱动注入?得额外引入
yii2-annotation-injector,但它增加反射开销,且与核心容器逻辑隔离
构造注入 vs setter 注入:选错影响对象不可变性
构造注入强制依赖在创建时就位,适合日志、数据库连接等“一旦创建就不能换”的核心服务;setter 注入允许后续重置,适合缓存前缀、租户 ID 等请求级可变参数。
- 构造注入的依赖无法在对象生命周期内更改,
__construct(Bar $bar)意味着$this->bar是只读契约 - setter 注入的属性可能被多次覆盖,比如
$service->setCacheKey('v2');,适合上下文敏感场景 - 循环依赖(A 依赖 B,B 又依赖 A)在构造注入下必然失败;改用 setter 注入 + 手动
set可破环,但本质是设计缺陷,应优先重构 - 性能上,构造注入更轻量(一次解析),setter 注入多一次方法调用 + 配置合并逻辑
真正容易被忽略的是:Yii 容器本身不管理对象销毁或资源释放,所有 __destruct() 或 close() 逻辑必须由业务类自行处理;容器只管“生”,不管“死”。











