Yii 2 中用 set() 绑定接口到具体类需在容器初始化前执行,如应用配置的 container.definitions 中,支持类名字符串、匿名函数或配置数组;接口名须为完整命名空间,如 'app\interfaces\LoggerInterface'。

Yii 2 中怎么用 set() 绑定接口到具体类
Yii 2 的依赖注入容器靠 set() 和 setSingleton() 建立接口与实现的映射。绑定接口不是“声明一下就行”,必须在容器初始化完成前执行,典型位置是应用配置的 container 数组或 bootstrap 阶段。
常见错误是把绑定写在控制器里、或者在 __construct() 中调用 Yii::$app->getContainer()->set(...) —— 此时容器已冻结,绑定无效,后续 createObject() 仍会报 InvalidConfigException: Failed to instantiate component。
-
set()每次请求都新建实例;setSingleton()全局只创建一次(适合数据库连接、缓存等) - 接口名必须是完整命名空间字符串,比如
'app\interfaces\LoggerInterface',不能省略app\ - 第二个参数可以是类名字符串、匿名函数、或配置数组;用匿名函数时注意闭包里别引用 $this,否则可能引发序列化失败
return [
'container' => [
'definitions' => [
'app\interfaces\PaymentGateway' => 'app\services\AlipayService',
'app\interfaces\CacheInterface' => [
'class' => 'yii\caching\FileCache',
'cachePath' => '@runtime/cache',
],
],
],
];
Yii 3 的 di 配置怎么写才不被忽略
Yii 3 彻底转向 PSR-11 兼容容器,不再有全局 Yii::$container。所有绑定必须通过 di 配置块定义,并在 ApplicationFactory 初始化时传入。漏掉这一步,哪怕写了 set() 也没用 —— 容器根本没加载你的规则。
典型陷阱是照搬 Yii 2 的写法,在 config/web.php 里直接写 Yii::getContainer()->set(...),结果运行时报 NotFoundException: No entry or class found for 'app\interfaces\NotifierInterface'。
- Yii 3 的
di配置是顶层键,和params、services并列,不是嵌套在container下 - 接口绑定支持字符串、闭包、
Reference对象;用Reference::to()可指向另一个已定义的服务名 - 若类构造函数参数类型提示是接口,但该接口没在
di中定义,容器连反射都过不去,直接抛ReflectionException
'di' => [
'app\interfaces\NotifierInterface' => 'app\notifiers\EmailNotifier',
'app\services\OrderService' => [
'__class' => 'app\services\OrderService',
'__construct()' => ['app\interfaces\NotifierInterface'],
],
],
为什么 new 出来的对象不走 DI 容器
这是最常被误解的一点:DI 容器只接管通过 Yii::createObject()(Yii 2)或 $container->get()(Yii 3)创建的对象。直接 new OrderService(),不管它构造函数参数是什么类型,容器完全不参与,也不会自动注入依赖。
后果很直接:你绑定了 LoggerInterface,但在某个服务里手写 new FileLogger(),那这个日志实例就和容器里配的 FileLogger 不是一回事,配置、单例、AOP 都失效。
- Yii 2 中,所有组件(
components配置项)、行为(behaviors)、以及createObject()创建的对象才走容器 - Yii 3 中,只有显式调用
$container->get(MyService::class)或通过构造函数参数自动解析时才触发注入 - 控制器动作方法的参数如果类型提示了接口,Yii 3 会自动尝试从容器取;但 Yii 2 不支持这种自动解析,必须手动
Yii::createObject()
接口绑定后,怎么验证它真生效了
别只看代码没报错就以为绑对了。实际运行中,最容易出问题的是「绑定生效了,但用错了地方」—— 比如绑了 CacheInterface 到 ApcCache,结果某处又硬编码 new FileCache(),或者用了 Yii::$app->cache 这个独立组件而非容器注入的实例。
验证方式很简单:在目标类构造函数里打个断点或 var_dump(get_class($dependency)),确认注入进来的是你指定的实现类,而不是默认的、或别的配置覆盖后的类。
- Yii 2:在
config/web.php里加'on beforeRequest' => function () { var_dump(Yii::$container->get('app\interfaces\LoggerInterface')); } - Yii 3:启动时加
echo get_class($container->get(LoggerInterface::class));,确保输出是你期望的类名 - 注意环境差异:开发环境可能用了 mock 实现,生产环境却忘了配,导致上线后行为突变
接口绑定本身不复杂,难的是让整个调用链都走容器路径。一旦中间某个环节绕开容器(比如静态工厂方法、全局函数、第三方库回调),前面所有绑定就形同虚设。


















