文档目录
依赖注入容器

简介

本技术文档围绕 DouPHP 的轻量级依赖注入容器,系统阐述其服务注册、解析、生命周期管理与单例模式实现;深入解释依赖解析算法(构造函数注入、方法注入、上下文绑定);记录服务提供者的工作机制(服务绑定、模块初始化、条件装配);并说明扩展点(自定义解析器、装饰器与代理对象)。文档同时面向初学者解释依赖注入概念与收益,并为高级开发者提供定制与高级特性使用指南。

项目结构

DouPHP 的依赖注入能力集中在核心基础层:

  • 容器核心:位于 core/foundation/container/Container.php,提供绑定、工厂、单例、别名、上下文绑定、反射构建与方法调用等能力。
  • 门面基类:位于 core/foundation/facade/StaticFacade.php,提供静态门面到容器的透明代理。
  • 服务提供者:位于 core/foundation/provider/*,按能力域将接口绑定到具体实现或降级实现,支持运行时条件装配。
  • 应用启动期装配:后台入口 admin/init/Init.php 在启动阶段注册并提供容器实例,完成语言、插件、视图引擎等关键服务的装配。
  • 插件体系集成:插件注册器通过 Container::getInstance() 获取容器并解析 Provider 与插件类,体现容器在扩展点中的枢纽作用。
graph TB
A["应用启动<br/>admin/init/Init.php"] --> B["容器实例<br/>Container::getInstance()"]
B --> C["服务提供者<br/>DataServiceProvider / LanguageServiceProvider / PluginServiceProvider"]
C --> D["接口绑定/工厂<br/>make()/singleton()/factory()"]
B --> E["门面代理<br/>StaticFacade"]
B --> F["插件注册器<br/>ConnectPluginRegistry"]
F --> B

核心组件

  • 容器 Container:提供 bind/singleton/factory/instance/alias/make/call/when()->needs()->give() 等 API,内置反射构建与方法参数自动注入,支持上下文绑定与构建栈追踪。
  • 服务提供者 Provider:以静态 register(Container) 方式向容器注册工厂或单例,根据配置与磁盘存在性选择真实现或降级实现,保证可插拔与向后兼容。
  • 门面 StaticFacade:以静态 __callStatic 转发到底层实例,底层实例优先从 swap 槽解析,其次从容器解析,便于测试替换与简化调用。

架构总览

容器作为依赖管理中心,贯穿启动装配、服务解析、插件加载与门面访问。服务提供者负责“何时绑定什么实现”,容器负责“如何解析与缓存”,门面提供“便捷访问”。

sequenceDiagram
participant App as "应用/控制器"
participant Facade as "StaticFacade"
participant Ctr as "Container"
participant Prov as "服务提供者"
participant Impl as "具体实现/降级实现"
App->>Facade : 静态方法调用
Facade->>Ctr : 解析 accessor 对应实例
Ctr->>Prov : 若未绑定则触发注册(启动期已执行)
Ctr->>Ctr : make()/singleton()/factory()
Ctr-->>Impl : 反射构建/工厂创建/返回缓存
Impl-->>Facade : 返回实例
Facade-->>App : 调用目标方法并返回结果

详细组件分析

容器 Container:服务注册、解析与生命周期

  • 服务注册
    • bind:抽象到实现的非单例绑定。
    • singleton:标记为单例,首次解析后缓存。
    • factory:以闭包按需创建,覆盖先前 instance 缓存。
    • instance:直接注入已有实例,覆盖 factory。
    • alias:别名映射,解析时转发。
  • 解析流程
    • make:别名转发 → 工厂调用 → 单例命中 → 解析具体类 → 反射构建 → 单例缓存。
    • call:反射方法参数自动注入,支持上下文覆盖参数(如场景)。
    • build:无构造函数直接 new;否则解析参数依赖链,异常包含构建栈信息。
    • resolveDependencies:优先手动覆盖 → 类型提示 → 上下文绑定 → FormRequest 特殊处理 → 递归 make → 默认值/可空处理 → 无法解析抛出错误。
  • 上下文绑定
    • when(consumer)->needs(abstract)->give(concrete|callable):仅对当前消费者生效,避免全局污染。
  • 状态查询与重置
    • has/bound/resolved/forgetInstance/reset:便于测试隔离与运行期诊断。
flowchart TD
Start(["make(abstract, parameters)"]) --> Alias{"是否别名?"}
Alias -- 是 --> ResolveAlias["转发到目标 abstract"]
Alias -- 否 --> Factory{"是否工厂?"}
Factory -- 是 --> CallFactory["调用工厂闭包"]
Factory -- 否 --> Singleton{"是否单例且已缓存?"}
Singleton -- 是 --> ReturnCached["返回缓存实例"]
Singleton -- 否 --> Concrete{"解析具体类"}
Concrete --> Build["反射构建/解析依赖"]
Build --> SaveSingleton{"是否单例?"}
SaveSingleton -- 是 --> Cache["写入单例缓存"]
SaveSingleton -- 否 --> ReturnInst["返回实例"]
CallFactory --> ReturnInst
ReturnCached --> End(["结束"])
ReturnInst --> End
Cache --> End

服务提供者:条件装配与降级策略

  • DataServiceProvider:根据 features.data 与类是否存在,绑定 DataServiceContract 到真实实现或 NullDataService。
  • LanguageServiceProvider:根据 features.language 与类是否存在,分别绑定 LanguageContract 与 AdminLanguageContract,缺失时回退到 NullLanguageService。
  • PluginServiceProvider:根据 plugin 模块是否可用,绑定 PluginServiceContract 到真实实现或 NullPluginService。
  • 特点:允许重复调用 register 覆盖工厂;通过 class_exists 与 Config 双闸门确保稳定降级。

门面 StaticFacade:静态代理与测试替换

  • getFacadeRoot:优先从 swap 槽取实例,否则从容器解析 accessor。
  • swap/clearResolvedInstance/clearAllResolvedInstances:测试期替换与清理。
  • __callStatic:将静态调用转发到底层实例同名方法。

启动期装配与容器使用

  • 后台 Init.php:
    • 获取容器实例,注册 Provider,解析并锁定语言服务与插件服务为单例。
    • 注册视图引擎与模板渲染接口。
    • 计算 ROOT_URL 并同步 Request base URL。
    • 按需注册导航、缓存、主题设置等组件。
  • 插件 ConnectPluginRegistry:
    • 通过 Container::getInstance() 解析插件 Provider 与插件类,体现容器在扩展点中的中心地位。

依赖解析算法详解

  • 构造函数注入:基于反射读取参数类型,递归 make 依赖链;支持默认值与可空参数;无法解析时抛出带构建栈的错误信息。
  • 方法注入:call() 对任意对象方法进行参数自动注入,支持上下文覆盖参数(如场景)。
  • 属性注入:容器未直接提供属性注入 API;可通过工厂闭包或外部逻辑在构造后赋值,或使用上下文绑定影响构造依赖。
  • 上下文绑定:when()->needs()->give() 针对特定消费者切换依赖实现,避免全局污染。
classDiagram
class Container {
+bind(abstract, concrete)
+singleton(abstract, concrete)
+factory(abstract, factory)
+instance(abstract, instance)
+alias(alias, target)
+make(abstract, parameters)
+call(instance, method, contextOverrides)
+when(consumer) ContextualBindingBuilder
+has(abstract) bool
+bound(abstract) bool
+resolved(abstract) bool
+reset()
}
class ContextualBindingBuilder {
+needs(abstract) ContextualBindingBuilder
+give(concrete) void
}
Container --> ContextualBindingBuilder : "创建"

依赖关系分析

  • 容器与服务提供者:Provider 通过 container->factory/bind/singleton/instance 注册实现;容器在 make 时按优先级解析。
  • 门面与容器:门面通过 getFacadeRoot 从容器解析底层实例,实现解耦与可测试性。
  • 插件与容器:插件注册器通过容器解析 Provider 与插件类,形成可扩展生态。
  • 启动期与容器:Init.php 在应用启动阶段完成关键服务装配,确保后续请求能稳定解析。
graph LR
Init["admin/init/Init.php"] --> C["Container"]
C --> P1["LanguageServiceProvider"]
C --> P2["DataServiceProvider"]
C --> P3["PluginServiceProvider"]
C --> F["StaticFacade"]
C --> R["ConnectPluginRegistry"]

性能与内存管理

  • 单例缓存:singleton/instance 将实例缓存在 singletons 数组中,减少重复构建成本。
  • 工厂覆盖语义:factory 会清除同 abstract 的 instance 缓存与单例标记,避免不一致;instance 会清除 factory,确保最后写入者胜出。
  • 反射开销:build 使用 ReflectionClass/ReflectionMethod,建议对高频路径使用单例或工厂预构建。
  • 构建栈与错误信息:resolveDependencies 在无法解析时输出构建栈,便于定位循环依赖或类型缺失问题。
  • 内存管理建议:
    • 长生命周期服务使用 singleton;短生命周期服务使用 factory。
    • 大对象尽量延迟构建(懒加载),必要时显式 forgetInstance 释放。
    • 测试环境使用 reset 清空容器状态,避免跨用例污染。

故障排查指南

  • 无法解析参数:检查类型提示是否正确、是否已绑定、是否存在默认值或可空;查看错误信息中的构建栈定位依赖链。
  • 上下文绑定未生效:确认 when(consumer) 的消费者类名与解析时的栈顶一致;仅在构造函数依赖解析时生效。
  • 单例未更新:若使用 factory 覆盖,需重新 make;若使用 instance 覆盖,会清除 factory 绑定。
  • 门面调用失败:确保 accessor 已在容器中注册;测试期可使用 swap 注入 mock。

结论

DouPHP 的依赖注入容器以简洁的 API 实现了完整的 DI 能力:服务注册、反射构建、方法注入、上下文绑定与单例管理;服务提供者以条件装配保障模块可插拔与降级;门面提供便捷的静态访问与测试替换;启动期装配确保关键服务在请求前就绪。该设计兼顾易用性与扩展性,适合中小型项目快速迭代与大型项目模块化治理。

附录:使用示例与最佳实践

  • 服务注册与解析
    • 在 Provider 中使用 factory/bind/singleton 注册接口到实现。
    • 在业务代码中通过 Container::getInstance()->make(Interface::class) 解析。
    • 参考路径:DataServiceProvider.php:39-48、LanguageServiceProvider.php:39-56、PluginServiceProvider.php:40-49。
  • 构造函数注入
    • 在服务类构造函数声明类型提示依赖,容器自动解析并注入。
    • 参考路径:Container.php:318-401。
  • 方法注入
    • 使用 Container::call($instance, $method, $contextOverrides) 自动注入方法参数。
    • 参考路径:Container.php:296-309。
  • 上下文绑定
    • 使用 when(Consumer::class)->needs(Abstract::class)->give(Concrete::class) 针对特定消费者切换实现。
    • 参考路径:Container.php:225-253、Container.php:444-454。
  • 门面使用
    • 通过 StaticFacade 子类实现 getAccessor 并调用静态方法,底层由容器解析。
    • 参考路径:StaticFacade.php:59-122。
  • 启动期装配
    • 在 Init.php 中注册 Provider 并锁定关键服务为单例。
    • 参考路径:Init.php:130-329。
  • 插件集成
添加日期:2026-10-05