如何正确初始化依赖注入容器以避免属性值为 null

如何正确初始化依赖注入容器以避免属性值为 null

本文解释了在依赖注入(di)场景中,因对象创建时机不当导致类属性获取不到容器中已注册服务的问题,并提供正确的初始化顺序与实践建议。

在使用自定义 DI 容器时,一个常见但容易被忽视的陷阱是:对象的构造函数中过早访问尚未初始化的服务。你遇到的 $this->router 为 null,而 $this->di->get(‘router’) 却能正常返回值,其根本原因并非 DI 实现有误,而是 Cms 实例的创建发生在服务注册之前

回顾你的 Bootstrap 文件原始逻辑:

$di = new DI();
$cms = new Cms($di); // ❌ 此时 $di 中尚未注册 'router'

$services = require 'config/services.php';
foreach ($services as $service) {
    $provider = new $service($di);
    $provider->init(); // ✅ 此处才真正执行 $di->set('router', $router)
}

问题就在这里:Cms 构造函数中调用了 $this->di->get(‘router’),但此时 Provider::init() 还未执行,$di->container[‘router’] 根本不存在,has() 方法中的空合并操作 ?? null 直接返回 null —— 因此 $this->router 被赋值为 null 并永久固化在实例属性中,后续即使 DI 容器补上了该服务,也不会自动更新已存在的属性。

✅ 正确做法是:确保所有服务注册完成之后,再创建依赖这些服务的对象。修正后的启动流程如下:

海螺视频

海螺视频

海螺AI推出的AI视频生成工具,可以生成高质量的视频内容。

下载

try {
    $di = new DI();

    // ✅ 先完成全部服务注册
    $services = require 'config/services.php';
    foreach ($services as $service) {
        (new $service($di))->init();
    }

    // ✅ 再创建 Cms 实例(此时 'router' 已存在于 $di 中)
    $cms = new Cms($di);
    $cms->run();
} catch (/ErrorException $e) {
    echo $e->getMessage();
}

此外,还建议对 DI::get() 方法做健壮性增强,避免静默返回 null 导致难以调试:

public function get($key)
{
    if (!array_key_exists($key, $this->container)) {
        throw new /InvalidArgumentException("Service '{$key}' is not registered in DI container.");
    }
    return $this->container[$key];
}

⚠️ 注意:当前 DI::get() 实际调用了 has(),而 has() 返回的是值或 null,这掩盖了“键不存在”的语义。应区分「键存在但值为 null」和「键不存在」两种情况——生产环境推荐显式校验并抛出异常,而非默认兜底。

最后,若希望 Cms 更具可测试性与松耦合性,还可考虑将 $router 改为延迟加载(Lazy Load)或通过方法注入替代构造注入:

public function getRouter(): Router
{
    if ($this->router === null) {
        $this->router = $this->di->get('router');
    }
    return $this->router;
}

这样既保持了构造轻量,又规避了初始化顺序风险。总结一句话:DI 的威力不在于“能存能取”,而在于“何时存、何时取”的严格时序控制。

https://www.php.cn/faq/2034593.html

发表回复

Your email address will not be published. Required fields are marked *