文档目录
工厂闭包绑定

简介

本文聚焦 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() 清空容器状态,或在测试中替换全局容器实例。
  • 错误边界:在工厂内进行输入校验与异常抛出,便于快速定位问题。
添加日期:2026-10-05