文档目录
容器核心功能

简介

本文件聚焦 DouPHP 依赖注入容器的核心能力,面向初学者解释“什么是依赖注入、为什么需要容器”,同时为高级开发者深入剖析容器内部机制:服务注册(bind/singleton/factory)、实例解析(make)、构造函数自动注入(反射+类型提示+依赖链构建)、上下文绑定(when()->needs()->give())、状态查询(has/bound/resolved)与重置(reset),以及全局访问机制(单例)。文档以源码为依据,提供流程图与时序图帮助理解。

项目结构

DouPHP 的 DI 容器位于 core/foundation/container 下,包含核心类 Container 与一组便捷辅助函数 helpers.php;在 admin/api 启动阶段通过 Init.php 将各类服务注册到容器中,形成应用级装配。

graph TB
A["应用入口<br/>admin/api 启动"] --> B["容器初始化<br/>Container::getInstance()"]
B --> C["服务注册<br/>bind / singleton / factory / instance"]
C --> D["业务代码调用<br/>app()/make()"]
D --> E["解析与构建<br/>反射 + 依赖注入"]
E --> F["返回实例<br/>单例缓存/工厂/上下文绑定"]

核心组件

  • 容器类 Container:实现服务注册、解析、生命周期管理、上下文绑定、状态查询与重置。
  • 上下文绑定构建器 ContextualBindingBuilder:支持 when()->needs()->give() 的链式声明。
  • 辅助函数 helpers.php:提供 app() 等便捷入口,统一从全局容器解析服务。

架构总览

容器采用“注册-解析”模式:

  • 注册阶段:在应用启动时(admin/api Init)将服务以 bind/singleton/factory/instance 方式注册。
  • 解析阶段:业务代码通过 app()/make() 请求服务,容器根据注册信息决定创建策略,并通过反射完成构造函数参数自动注入。
  • 生命周期:singleton 与 instance 会缓存实例;factory 每次调用都会执行闭包;普通 bind 每次解析新建实例。
sequenceDiagram
participant App as "业务代码"
participant Helper as "app()"
participant C as "Container"
participant R as "反射/构建"
participant S as "服务实现"
App->>Helper : app(ClassName : : class, params)
Helper->>C : make(abstract, parameters)
alt 已注册工厂
C-->>App : 调用工厂闭包并返回
else 已缓存单例
C-->>App : 直接返回缓存实例
else 普通绑定或类名
C->>R : build(concrete, parameters)
R->>R : 解析构造函数参数反射+类型提示
R->>S : new Concrete(...)
R-->>C : 返回实例
opt 单例
C->>C : 缓存实例
end
C-->>App : 返回实例
end

详细组件分析

服务注册机制(bind、singleton、factory、instance)

  • bind:将抽象(接口/别名/类名)映射到具体类或字符串类名,每次解析新建实例。
  • singleton:标记为单例,首次解析后缓存实例,后续直接返回。
  • factory:用闭包控制创建逻辑,覆盖先前 instance 缓存,每次 make 都执行工厂。
  • instance:直接注入已有实例,覆盖先前 factory,之后 make 始终返回该实例。
flowchart TD
Start(["注册入口"]) --> Bind{"是否 bind?"}
Bind --> |是| SaveBind["保存 bindings[abstract] = concrete"]
Bind --> |否| Singleton{"是否 singleton?"}
Singleton --> |是| MarkSingle["标记 _singleton=true 并写入 singletons[abstract]=null"]
Singleton --> |否| Factory{"是否 factory?"}
Factory --> |是| SaveFactory["保存 factories[abstract] = callable"]
Factory --> |否| Instance{"是否 instance?"}
Instance --> |是| SaveInstance["保存 singletons[abstract] = $instance"]
Instance --> |否| End(["结束"])

实例解析算法(make)

make 的核心流程:

  1. 别名转发:若 abstract 是别名,则递归解析目标。
  2. 工厂优先:若存在工厂闭包,直接调用并返回。
  3. 单例命中:若为单例且已缓存,直接返回。
  4. 解析具体类:从 bindings 取 concrete,否则直接使用 abstract。
  5. 构建对象:调用 build 进行反射构造与依赖注入。
  6. 单例缓存:若为单例,缓存实例后返回。
flowchart TD
MStart(["make(abstract, parameters)"]) --> Alias{"有别名?"}
Alias --> |是| Recur["make(target, parameters)"]
Alias --> |否| FactoryCheck{"有工厂?"}
FactoryCheck --> |是| CallFactory["call_user_func(factory, container)"]
FactoryCheck --> |否| SingleCheck{"单例且已缓存?"}
SingleCheck --> |是| ReturnCached["返回 singletons[abstract]"]
SingleCheck --> |否| ResolveConcrete["concrete = bindings[abstract] ?? abstract"]
ResolveConcrete --> Build["build(concrete, parameters)"]
Build --> SingletonCache{"是否单例?"}
SingletonCache --> |是| Cache["singletons[abstract] = instance"]
SingletonCache --> |否| ReturnInst["返回 instance"]

构造函数自动注入(反射、类型提示、依赖链)

  • 反射获取构造函数参数列表。
  • 对每个参数:
    • 优先使用传入的 overrides(含 __scene 等特殊键)。
    • 读取类型提示(兼容 PHP 5.6/8.x),若为 FormRequest 子类则按场景构造。
    • 检查上下文绑定(当前消费者 needs 指定实现)。
    • 若无类型提示但有默认值或可空,则使用默认值或 null。
    • 否则抛出运行时异常,错误信息包含依赖链(buildStack)。
  • 使用 ReflectionClass::newInstanceArgs 组装依赖并创建实例。
flowchart TD
BStart(["build(concrete, parameters)"]) --> Refl["ReflectionClass(concrete)"]
Refl --> CheckInstantiable{"可实例化?"}
CheckInstantiable --> |否| ThrowNotInst["抛出不可实例化异常"]
CheckInstantiable --> |是| GetCtor["getConstructor()"]
GetCtor --> HasCtor{"有无构造函数?"}
HasCtor --> |无| NewDirect["new concrete()"]
HasCtor --> |有| PushStack["push buildStack"]
PushStack --> Resolve["resolveDependencies(params, overrides)"]
Resolve --> PopStack["pop buildStack"]
PopStack --> NewArgs["newInstanceArgs(dependencies)"]
NewArgs --> ReturnObj["返回对象"]

上下文绑定(when()->needs()->give())

  • when(consumer) 返回 ContextualBindingBuilder。
  • needs(abstract) 指定消费者需要的抽象。
  • give(concrete|callable) 在当前消费者上下文中覆盖该抽象的实现。
  • 解析时仅在 resolveContextual 中查看栈顶消费者的上下文映射,避免影响其他消费者。
sequenceDiagram
participant Dev as "开发者"
participant C as "Container"
participant B as "ContextualBindingBuilder"
Dev->>C : when(Consumer : : class)
C-->>Dev : B
Dev->>B : needs(Abstract : : class)
Dev->>B : give(Concrete : : class|callable)
B->>C : addContextualBinding(Consumer, Abstract, Concrete)

状态管理与重置(has/bound/resolved/reset)

  • has/bound:判断抽象是否已注册(包括别名、绑定、工厂、单例)。
  • resolved:判断单例是否已实例化。
  • forgetInstance:移除指定单例实例(保留绑定,下次解析重建)。
  • reset:清空所有绑定、单例、工厂、上下文、别名与构建栈,用于测试隔离或重启。

全局访问机制(单例)

  • Container::getInstance:获取全局容器实例(延迟初始化)。
  • Container::setInstance:替换全局容器(测试或托管启动)。
  • helpers.php 中的 app():作为统一入口,返回容器本身或解析服务。

依赖关系分析

  • 容器依赖反射 API(ReflectionClass/ReflectionMethod/ReflectionParameter)进行依赖解析。
  • 与 FormRequest 集成:当参数类型为 FormRequest 子类时,通过场景参数构造。
  • 与业务模块解耦:通过 ProviderRegistry 在各端 Init 中注册服务,使容器成为装配中心。
graph LR
C["Container"] --> R["反射API"]
C --> FR["FormRequest"]
C --> PR["ProviderRegistry(外部)"]
PR --> SvcA["语言服务(LanguageContract)"]
PR --> SvcB["插件服务(PluginServiceContract)"]
PR --> SvcC["消息响应(MessageResponderInterface)"]

性能考量

  • 单例与实例缓存:singleton/instance 避免重复创建,降低开销。
  • 工厂闭包:适合昂贵对象的按需创建,但注意频繁调用带来的成本。
  • 反射开销:反射解析发生在首次构建时,后续可通过单例缓存减少重复反射。
  • 上下文绑定:仅作用于当前消费者,避免全局污染,提高解析效率。
  • 建议:
    • 将重资源服务注册为 singleton 或 instance。
    • 对可变状态的服务使用 factory 或 instance,避免共享状态导致并发问题。
    • 合理拆分大依赖树,减少深层依赖链导致的构建成本。

故障排查指南

常见错误与定位方法:

  • “Cannot resolve parameter [$name] in [Class]”:缺少依赖或未提供默认值/可空。检查依赖链(buildStack)与上下文绑定。
  • “is not instantiable”:目标类不可实例化(如抽象类/接口未绑定具体实现)。
  • 单例未生效:确认是否被 factory 覆盖或忘记注册为 singleton。
  • 上下文绑定无效:确认 when()->needs()->give() 是否在正确消费者上下文中声明。

调试技巧:

  • 使用 has/bound/resolved 检查注册与实例化状态。
  • 使用 reset 清理容器状态,便于测试隔离。
  • 在 make 前打印 buildStack 或临时记录日志,观察依赖解析顺序。

结论

DouPHP 的 DI 容器提供了轻量而强大的依赖注入能力:支持多种注册方式、自动构造函数注入、上下文绑定、状态查询与重置,并通过全局单例与辅助函数简化使用。结合各端 Init 的装配,容器成为系统服务的统一管理中心,既满足初学者易用性,又为高级用户提供深入控制点。

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

  • 基本绑定与解析
    • 将接口绑定到实现:在 Init 中使用 bind 或 provider 注册。
    • 解析服务:通过 app(ClassName::class) 或 Container::getInstance()->make(...)。
  • 单例与工厂
    • 单例:singleton 或 instance 用于共享状态或昂贵对象。
    • 工厂:factory 用于复杂创建逻辑或条件创建。
  • 上下文绑定
    • when(Consumer::class)->needs(Abstract::class)->give(Concrete::class) 针对特定消费者切换实现。
  • 状态查询与重置
    • has/bound 检查注册;resolved 检查单例实例化;reset 用于测试隔离。
  • 全局访问
    • 使用 app() 作为统一入口;必要时通过 Container::getInstance() 直接操作容器。
添加日期:2026-10-05