CI4服务容器默认单例,覆盖必须在首次调用前完成,否则无效;injectMock需在测试开头注入;自定义服务方法名须合法且静态;环境判断依赖ENVIRONMENT常量,配置未加载时避免调用config()。

为什么service()调用返回旧实例?
CI4 的服务容器默认对每个服务类型只创建一次实例(单例),后续调用 service('session') 或 \Config\Services::session() 都会复用已有实例。如果你在测试或开发中手动覆盖了某个服务,但后续仍拿到原始实例,大概率是因为覆盖时机太晚——服务已被首次调用并缓存。
- 服务一旦被首次访问(如
session()、database()),容器就将其注册进内部缓存,后续所有service()调用都直接返回该缓存对象 - 覆盖操作必须在任何服务被首次调用前执行,最佳位置是
app/Config/Services.php的静态方法内,或app/Config/Boot/production.php等早期加载文件中 - 若在控制器
__construct()里调用$container->singleton('session', ...),此时session()很可能已被框架前置逻辑调用过,覆盖无效
Services::injectMock() 在测试中失效
单元测试里用 \Config\Services::injectMock('cache', $mock) 却发现控制器仍拿到真实缓存实例,常见于测试启动流程不完整或 Mock 注入顺序错误。
-
injectMock()只对后续新创建的服务生效;已存在的服务实例(比如测试前由Services::cache()创建的)不会被替换 - 务必在测试用例最开头、任何服务调用之前注入 Mock,例如放在
setUp()开头,而非构造函数或具体测试方法内 - 若使用 CodeIgniter 的
TestCase基类,请确认未提前触发$this->getRoute(...)或$this->call(...)—— 这些方法内部会预加载请求、响应等服务,导致缓存已建立
自定义服务类没被正确注册
你写了新服务类 App\Services\MyLogger,并在 app/Config/Services.php 中添加了 public static function mylogger() 方法,但 service('mylogger') 报错说服务不存在。
- CI4 服务注册依赖「方法名即服务名」,且方法必须是
public static,返回实例;方法名不能含下划线(如my_logger不合法,应为mylogger) - 确保该方法在
Services类中定义,且未被parent::调用覆盖(不要在子类里重写整个Services类,而应在原类中追加) - 如果服务需依赖其他服务(如
MyLogger依赖CacheInterface),构造函数参数必须带类型提示,且该依赖本身已在容器中可解析(否则抛出ReflectionException)
环境配置导致覆盖被跳过
你在 app/Config/Services.php 的 database() 方法里加了条件判断,开发环境返回 Mock 实例,生产环境返回真实连接,但部署后始终走真实连接。
- CI4 的
ENVIRONMENT常量在public/index.php中定义,早于Services类加载;但某些部署方式(如 CLI 启动、队列任务)可能未正确设置该常量 - 检查实际运行时的
ENVIRONMENT值:var_dump(ENVIRONMENT);,避免依赖$_SERVER['CI_ENV']等不可靠来源 - 不要在
Services方法里做复杂逻辑,尤其避免调用config('Database')—— 此时配置可能尚未加载,导致意外 fallback 到默认值

















