简介
本文聚焦 DouPHP 容器的“工厂闭包绑定”能力,围绕 Container::factory() 的注册机制、执行流程、覆盖语义以及与容器实例的交互方式展开。文档面向高级开发者,提供复杂对象创建逻辑的实现范式(条件化实例化、依赖注入、错误处理)、嵌套依赖解析路径、性能优化建议与常见陷阱规避(循环依赖、内存泄漏)。
项目结构
DouPHP 将容器实现集中在核心基础层,并通过服务提供者按需注册工厂闭包;应用启动阶段在后台与 API 入口中完成特性开关加载后重新注册,确保最终运行时使用正确的实现。
graph TB
A["Container.php<br/>容器核心"] --> B["ProviderRegistry.php<br/>Provider 注册总线"]
B --> C["LanguageServiceProvider.php"]
B --> D["DataServiceProvider.php"]
B --> E["PluginServiceProvider.php"]
F["admin/init/Init.php<br/>后台启动"] --> B
G["api/init/Init.php<br/>API 启动"] --> B
图示来源
- Container.php:123-155
- ProviderRegistry.php:37-56
- LanguageServiceProvider.php:39-56
- DataServiceProvider.php:39-48
- PluginServiceProvider.php:40-49
- Init.php(后台):142-149
- Init.php(API):134-136
核心组件
- 容器 Container:提供 bind/singleton/instance/factory/alias/make/call 等能力,维护绑定表、单例缓存、工厂闭包表、上下文绑定与别名映射。
- ProviderRegistry:集中注册各平台能力 Provider,统一调用 Provider::register(Container)。
- 服务提供者 LanguageServiceProvider、DataServiceProvider、PluginServiceProvider:根据配置与模块可用性,向容器注册工厂闭包,返回真实现或 Null 占位实现。
- 启动初始化 Init(后台/API):在特性开关就绪后重新注册 Provider,并将关键服务以 instance() 锁定为单例。
架构总览
工厂闭包绑定的生命周期如下:
- 注册阶段:ProviderRegistry 依次调用各 Provider::register(Container),通过 Container::factory() 将抽象接口绑定到工厂闭包。
- 运行阶段:业务代码通过 Container::make() 解析抽象;若存在工厂闭包则直接调用并返回结果。
- 覆盖阶段:后续可通过 instance() 将已有实例锁为单例,覆盖先前 factory 绑定;也可再次 factory() 覆盖 instance 缓存。
sequenceDiagram
participant App as "应用启动"
participant Reg as "ProviderRegistry"
participant Prov as "各 Provider"
participant C as "Container"
participant Biz as "业务代码"
App->>Reg : registerAll(container)
Reg->>Prov : 调用 Provider : : register(container)
Prov->>C : factory(抽象, 工厂闭包)
Note over C : 记录工厂闭包,清除同抽象的单例标记与缓存
App-->>Biz : 进入业务请求
Biz->>C : make(抽象)
C-->>Biz : 调用工厂闭包并返回实例
Biz->>C : instance(抽象, 实例)
C-->>Biz : 覆盖工厂,锁定单例
图示来源
- ProviderRegistry.php:49-56
- LanguageServiceProvider.php:39-56
- DataServiceProvider.php:39-48
- PluginServiceProvider.php:40-49
- Container.php:123-155
- Container.php:262-294
详细组件分析
Container::factory() 工作原理
- 注册机制:将工厂闭包写入 factories 表,同时移除同 abstract 的单例标记与缓存,避免 make() 命中旧分支。
- 执行流程:make() 优先检查别名→工厂→单例缓存→具体类反射构建;当存在工厂时直接调用闭包并返回结果。
- 覆盖语义:“最后写入者胜出”。factory() 会覆盖 instance() 缓存;instance() 会覆盖 factory() 绑定。
flowchart TD
Start(["make(abstract)"]) --> Alias{"是否别名?"}
Alias --> |是| ResolveAlias["转发到目标抽象"]
Alias --> |否| CheckFactory{"是否存在工厂?"}
CheckFactory --> |是| CallFactory["调用工厂闭包并返回"]
CheckFactory --> |否| CheckSingleton{"是否已缓存单例?"}
CheckSingleton --> |是| ReturnInstance["返回缓存实例"]
CheckSingleton --> |否| Build["反射构建/递归解析依赖"]
Build --> MaybeCache{"是否单例?"}
MaybeCache --> |是| Store["写入单例缓存"]
MaybeCache --> |否| Skip["跳过缓存"]
Store --> Return["返回实例"]
Skip --> Return
ReturnInstance --> End(["结束"])
CallFactory --> End
Return --> End
图示来源
- Container.php:262-294
- Container.php:123-155
服务提供者中的工厂闭包示例
- 语言服务:根据 features.language 与模块类存在性,返回 LanguageService 或 NullLanguageService。
- 数据服务:根据 features.data 与模块类存在性,返回 DataService 或 NullDataService。
- 插件服务:根据模块类存在性,返回 PluginService 或 NullPluginService。
classDiagram
class Container {
+factory(abstract, factory)
+make(abstract, parameters)
+instance(abstract, instance)
}
class LanguageServiceProvider {
+register(container)
}
class DataServiceProvider {
+register(container)
}
class PluginServiceProvider {
+register(container)
}
Container <.. LanguageServiceProvider : "注册工厂"
Container <.. DataServiceProvider : "注册工厂"
Container <.. PluginServiceProvider : "注册工厂"
图示来源
- LanguageServiceProvider.php:39-56
- DataServiceProvider.php:39-48
- PluginServiceProvider.php:40-49
- Container.php:123-155
启动阶段的覆盖与锁定
后台与 API 启动流程在特性开关加载后,重新注册 Provider 以基于最新配置选择实现,随后用 instance() 将关键服务锁定为单例,避免每次解析都走工厂。
sequenceDiagram
participant Boot as "Init(后台/API)"
participant Reg as "ProviderRegistry"
participant C as "Container"
Boot->>Reg : registerAll(container)
Reg->>C : factory(...)
Boot->>C : instance(抽象, make(抽象))
Note over C : instance() 覆盖工厂,锁定单例
图示来源
- Init.php(后台):142-149
- Init.php(API):134-136
- ProviderRegistry.php:49-56
- Container.php:149-155
复杂对象创建逻辑范式
- 条件化实例化:在工厂闭包内依据配置或环境判断返回不同实现(如真实现 vs Null 占位)。
- 依赖注入:工厂闭包接收容器参数,可继续通过 make() 解析其他依赖,形成嵌套依赖链。
- 错误处理:在工厂内部进行前置校验与异常抛出;容器在反射构建阶段对不可实例化或缺失依赖给出明确错误信息。
flowchart TD
Enter(["进入工厂闭包"]) --> CheckCfg["读取配置/环境"]
CheckCfg --> Decision{"满足条件?"}
Decision --> |是| NewReal["new 真实现"]
Decision --> |否| NewNull["new Null 实现"]
NewReal --> InjectDeps["通过容器解析依赖"]
NewNull --> ReturnNull["返回 Null 实现"]
InjectDeps --> ReturnReal["返回真实现"]
图示来源
- LanguageServiceProvider.php:41-55
- DataServiceProvider.php:41-47
- PluginServiceProvider.php:42-48
- Container.php:318-344
依赖关系分析
- 松耦合:业务仅依赖抽象接口,由 Provider 决定具体实现。
- 覆盖顺序:factory → instance 覆盖;再次 factory 可覆盖 instance 缓存。
- 上下文绑定:when()->needs()->give() 可在特定消费者下切换依赖实现,不影响全局绑定。
graph LR
A["抽象接口"] --> B["工厂闭包"]
B --> C["真实现 / Null 实现"]
D["业务代码"] --> A
E["ProviderRegistry"] --> B
F["Init(后台/API)"] --> D
图示来源
- Container.php:123-155
- ProviderRegistry.php:49-56
- LanguageServiceProvider.php:39-56
- DataServiceProvider.php:39-48
- PluginServiceProvider.php:40-49
性能考量
- 工厂开销:每次 make() 命中工厂都会执行闭包,适合轻量判断;若构造昂贵,建议在工厂内缓存或使用 instance() 锁定单例。
- 单例锁定:在启动后期使用 instance() 锁定关键服务,避免重复解析与构造。
- 反射成本:容器通过反射构建对象并自动注入依赖,应避免在热路径频繁触发;必要时将结果缓存为单例。
- 内存管理:避免在工厂闭包中持有长生命周期引用导致内存泄漏;及时释放临时资源。
故障排查指南
- 无法解析参数:当构造函数参数既无默认值又不可空且未注册依赖时,容器抛出运行时异常,提示缺失参数及构建链。
- 非可实例化类:尝试实例化不可实例化的类时会抛出运行时异常。
- 类型兼容:PHP 8+ 类型严格,类型不匹配可能导致 TypeError;容器会在依赖解析阶段捕获并给出清晰错误。
结论
DouPHP 容器的工厂闭包绑定提供了灵活的对象创建机制,结合“最后写入者胜出”的覆盖语义,使系统能够在启动期按特性开关动态选择实现,并在运行期通过 instance() 锁定关键服务以提升性能。配合上下文绑定与自动依赖注入,能够优雅地组织复杂依赖关系,同时保持模块解耦与可测试性。
附录:最佳实践与高级定制
- 工厂职责单一:工厂闭包只做“选择实现”和“必要依赖组装”,避免承载业务逻辑。
- 条件判断前置:在 Provider 中集中判断配置与模块可用性,保证行为一致。
- 避免循环依赖:工厂内不要互相依赖同一抽象的不同实现;必要时拆分职责或使用延迟解析。
- 谨慎使用 instance():仅在确认对象稳定且需跨请求复用时锁定单例。
- 测试隔离:利用 reset() 清空容器状态,或在测试中替换全局容器实例。
- 错误边界:在工厂内进行输入校验与异常抛出,便于快速定位问题。