Yii::$app->get()调用的是Service Locator而非IoC容器,它仅按名称查找并缓存实例,不解析类型提示或自动注入依赖;真正的IoC功能由yii\di\Container提供,需显式调用且不干预应用组件获取流程。

Yii里Yii::$app->get()调的是Service Locator,不是IoC容器
很多人以为Yii的Yii::$app->get('db')是在用IoC容器做依赖注入,其实不是。Yii应用实例(Yii::$app)本身是一个ServiceLocator,它只负责“按名找对象”,不参与构造逻辑、不解析类型提示、也不自动注入依赖。
真正实现IoC能力的是yii\di\Container类,但它默认**不接管应用组件的获取流程**——除非你显式用它来构建对象,比如$container->get(MyService::class)。
-
ServiceLocator是“名字→实例”的简单映射,缓存已创建的实例,适合配置固定、生命周期长的组件(如db、cache) -
Container支持构造函数注入、方法注入、接口绑定、单例/原型作用域,但需要你主动调用它,且不会自动替换Yii::$app->get()的行为 - 如果你在
config/web.php里写'components' => ['db' => [...]],这些组件由ServiceLocator管理;而'container' => ['definitions' => [...]]才影响Container
想让Controller里自动注入服务?别指望ServiceLocator,得配Container并改用构造函数
Yii默认Controller是通过ServiceLocator创建的,所以__construct()参数不会被自动填充。哪怕你在container里绑定了MyService::class,new SiteController()也不会触发注入。
要启用构造函数注入,必须两步走:
- 在应用配置中设置
'container' => ['definitions' => [MyService::class => MyService::class]] - 把Controller注册为
Container可管理的对象,例如在bootstrap或模块中手动用$container->get(SiteController::class),而不是靠Yii::$app->createController()
否则你会看到Missing required parameters for controller constructor这类错误——这不是配置漏了,是根本没走Container的解析链。
Container和ServiceLocator混用时,set()行为完全不同
都叫set(),但语义天差地别:
-
$serviceLocator->set('db', $config):注册一个“工厂回调”,下次get('db')时才执行,返回单例实例 -
$container->set(MyService::class, $config):注册一个“类定义”,下次get(MyService::class)时才构造,可设为每次新建(['class' => ..., 'singleton' => false]) - 更关键的是:
$container->set('db', ...)不会影响Yii::$app->get('db'),因为ServiceLocator根本不查Container
常见坑:有人在container里set('db', ...),然后期待Yii::$app->get('db')返回新配置的实例——结果还是老的,因为两个系统完全隔离。
复杂依赖链下,Container能解耦,但ServiceLocator会悄悄藏住问题
比如OrderService依赖PaymentGateway,而后者又依赖Logger。用Container可以清晰绑定:$container->set(LoggerInterface::class, FileLogger::class),所有层级自动注入。
但如果全靠ServiceLocator,你得在OrderService里写Yii::$app->get('paymentGateway'),再在PaymentGateway里写Yii::$app->get('logger')——这等于把依赖硬编码进实现,测试时没法替换,也看不出哪里用了什么。
最隐蔽的问题是:当PaymentGateway某天开始需要一个新依赖,你得手动改它的构造函数+所有调用点,而Container只要补一条set()就能透传下去。没人报错,但重构成本已经悄悄翻倍。










