Yii框架中Yii::$app本身就是yii\di\ServiceLocator实例,所有应用组件(如db、cache)均通过它注册、延迟实例化并统一访问;注册方式包括配置数组(最常用)、类名字符串、闭包或对象实例,需避免启动时new导致失去懒加载优势;访问时__get()与get()行为基本一致,但get()支持静默失败;isset()仅判断定义存在,应使用has($id, true)确认实例可用;需提前加载的组件(如log、authChecker)应加入bootstrap数组。

Yii 框架里服务定位器不是“要学的新东西”,而是你每天都在用的 \Yii::$app 本身——它就是一个 yii\di\ServiceLocator 实例。所有像 db、cache、urlManager 这样的应用组件,都是通过它注册、延迟实例化并统一访问的。
怎么注册一个服务到 Service Locator
注册服务本质就是往 \Yii::$app->components 数组里加配置,或调用 set() 方法。关键在 $definition 的类型选择:
- 字符串类名(如
'yii\caching\ApcCache'):直接 new,无参数,适合无依赖的简单类 - 配置数组(最常用):必须含
class键,其余为构造参数或属性赋值,例如['class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=test'] - 闭包:可控制实例化逻辑,比如带条件初始化、复用已有对象,但注意闭包内不能直接引用未声明变量(常见坑)
- 对象实例:立即注册已存在的对象,但会绕过延迟加载机制,慎用
别在配置里写 new \SomeClass() —— 这会导致启动时就实例化,失去按需加载优势;也别把闭包写成 function () use ($someVar) { ... } 却没确保 $someVar 在闭包执行时还有效。
为什么 \Yii::$app->xxx 能直接访问,但 set() 后却报错 “not found”
常见错误是混淆了“定义”和“实例”。调用 set('myService', [...]) 只是把定义存进 $_definitions,第一次访问 \Yii::$app->myService 才触发实例化并缓存到 $_components。如果此时配置出错(比如类不存在、构造参数缺失),就会抛异常,且后续再访问仍失败——因为 $_components 没写入,$_definitions 却已存在,形成“定义了但无法实例化”的僵局。
排查方法:
- 用
\Yii::$app->has('myService', false)看是否已定义 - 用
\Yii::$app->has('myService', true)看是否已有实例(返回 false 表示没成功实例化过) - 检查异常堆栈,重点看
get()内部触发的构造过程,而非set()调用处
get() 和属性访问(\Yii::$app->xxx)行为一致吗
基本一致,但有微妙差异:
- 属性访问走的是重载的
__get(),内部调用get($name),所以行为相同 - 但
__get()会先查$_components缓存,再查$_definitions并尝试实例化;而手动调用get('xxx', false)可跳过异常抛出,返回 null - 更关键的是:
__get()不支持传参,get()第二个参数$throwException控制是否静默失败,这对容错场景(比如可选组件)很实用
所以线上代码别依赖 isset(\Yii::$app->xxx) 判断服务可用性——它只查 $_definitions,不保证能实例化;应改用 \Yii::$app->has('xxx', true)。
什么时候该用 bootstrap 强制提前加载组件
默认组件是懒加载的,但有些服务必须在请求处理早期就准备好,比如日志收集、性能监控钩子、权限预检等。这时要把组件 ID 加进应用配置的 bootstrap 数组:
'bootstrap' => ['log', 'authChecker'],
注意两点:
-
bootstrap中的组件会在Application::init()阶段就被get()一次,强制完成实例化和初始化逻辑 - 如果组件自身有副作用(如连接数据库、发 HTTP 请求),放在
bootstrap里会拖慢所有请求的启动速度,哪怕当前请求根本用不到它
真正需要 bootstrap 的,通常是那些监听事件、注入行为、影响全局状态的组件,而不是普通工具类。











