文档目录
容器扩展点

简介

本文件面向框架开发者,系统性说明 DouPHP 容器的扩展点机制。内容涵盖:

  • 容器的扩展架构设计:绑定、工厂、单例、上下文绑定、别名等扩展能力
  • 装饰器模式在容器中的落地方式:通过工厂闭包或上下文绑定对已有实现进行“包装”
  • 如何通过继承 Container 类扩展现有功能:重写 make()/build()/resolveDependencies() 等关键方法
  • 横切关注点的扩展示例:日志记录、性能监控、依赖验证
  • 安全考虑与最佳实践:向后兼容性与性能影响评估
  • 测试策略:如何在测试环境中隔离和验证扩展功能

项目结构

DouPHP 的 DI 容器位于 core/foundation/container 下,提供轻量级依赖注入与对象生命周期管理;引导阶段在 core/bootstrap.php 中完成全局容器实例化与关键服务注册;中间件系统通过 core/foundation/middleware/MiddlewareRegistry.php 使用容器解析并组装中间件链。

graph TB
A["应用启动<br/>core/bootstrap.php"] --> B["DI 容器实例化<br/>Container::getInstance()"]
B --> C["关键服务早绑定<br/>Request / DelegatingRouter"]
C --> D["业务模块通过 app()/make() 获取服务"]
D --> E["中间件链构建<br/>MiddlewareRegistry 使用容器解析"]

核心组件

  • 容器核心:Container(绑定、工厂、单例、上下文绑定、别名、反射自动注入)
  • 辅助函数:app() 作为容器语法糖,统一入口
  • 引导期注册:bootstrap.php 中提前注册 Request、DelegatingRouter 等关键对象
  • 中间件装配:MiddlewareRegistry 基于容器解析中间件实例,支持参数化中间件

架构总览

容器采用“声明式绑定 + 运行时解析”的模式:

  • 声明期:bind/singleton/factory/alias/when()->needs()->give()
  • 运行期:make() 根据抽象名解析具体实现,必要时通过 build() 反射构造并 resolveDependencies() 递归注入依赖
  • 上下文绑定:针对特定消费者(如控制器或服务)替换某依赖的实现,避免全局污染
  • 装饰器模式:通过 factory() 返回包装后的实例,或在 when()->needs()->give() 中给出一个工厂闭包,将原实现包裹进新的行为(如日志、监控、校验)
classDiagram
class Container {
-bindings : array
-singletons : array
-factories : array
-contextualBindings : array
-aliases : array
-buildStack : array
+bind(abstract, concrete) void
+singleton(abstract, concrete) void
+factory(abstract, factory) void
+instance(abstract, instance) void
+alias(alias, target) void
+has(abstract) bool
+bound(abstract) bool
+resolved(abstract) bool
+forgetInstance(abstract) void
+when(consumer) ContextualBindingBuilder
+make(abstract, parameters) mixed
+call(instance, method, contextOverrides) mixed
-build(concrete, parameters) mixed
-resolveDependencies(params, overrides) array
-getParamClass(param) ReflectionClass|null
-resolveContextual(abstract) string|callable|null
}
class ContextualBindingBuilder {
-container : Container
-consumer : string
-abstract : string|null
+__construct(container, consumer)
+needs(abstract) ContextualBindingBuilder
+give(concrete) void
}
Container --> ContextualBindingBuilder : "创建"

详细组件分析

容器核心流程:make()/build()/resolveDependencies()

  • make() 负责别名转发、工厂调用、单例缓存、解析具体类、调用 build()、单例缓存
  • build() 使用反射检查可实例化性,入栈当前消费者以支持上下文绑定,解析依赖后构造实例
  • resolveDependencies() 按参数顺序解析:优先手动覆盖、类型提示、上下文绑定、FormRequest 场景、默认值/可空处理,否则抛出异常
sequenceDiagram
participant Caller as "调用方"
participant C as "Container"
participant R as "反射/依赖解析"
Caller->>C : make(抽象名, 参数)
alt 已注册工厂
C-->>Caller : 调用工厂闭包并返回
else 单例已存在
C-->>Caller : 返回缓存实例
else 普通绑定/类名
C->>C : build(具体类, 参数)
C->>R : resolveDependencies(参数列表, 覆盖)
R-->>C : 依赖数组
C-->>Caller : 反射构造实例
opt 单例
C->>C : 缓存实例
end
end

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

  • when(consumer) 返回 ContextualBindingBuilder,用于为特定消费者声明 needs(abstract)->give(concrete)
  • give() 将抽象到实现的映射写入容器的 contextualBindings,仅作用于当前消费者
  • 解析时 resolveContextual() 读取栈顶消费者的上下文映射,优先于全局绑定
flowchart TD
Start(["开始"]) --> When["when(消费者)"]
When --> Needs["needs(抽象)"]
Needs --> Give["give(具体实现/工厂)"]
Give --> Store["写入 contextualBindings[消费者][抽象]"]
Store --> Resolve{"解析依赖时"}
Resolve --> |命中上下文| UseCtx["使用上下文实现"]
Resolve --> |未命中| Fallback["回退到全局绑定/类名"]
UseCtx --> End(["结束"])
Fallback --> End

装饰器模式的应用

  • 通过 factory() 返回包装后的实例,在不修改原类的情况下增加横切逻辑(如日志、监控、校验)
  • 通过 when()->needs()->give() 为特定消费者注入带装饰行为的实现,避免全局污染
  • 结合 call() 可在对象方法调用时自动注入依赖,便于在方法层做增强
sequenceDiagram
participant App as "应用"
participant C as "Container"
participant F as "工厂闭包"
participant W as "被装饰实现"
App->>C : make(抽象)
C->>F : 调用工厂闭包
F->>W : 创建原始实现
F-->>C : 返回包装后的实例
C-->>App : 返回装饰后的实例

中间件与容器的集成

  • MiddlewareRegistry 通过容器解析中间件别名对应的类,支持参数化中间件
  • 构造期异常向上抛出,确保 CSRF 等安全中间件不会因构造失败被静默跳过
sequenceDiagram
participant MR as "MiddlewareRegistry"
participant C as "Container"
MR->>C : make(中间件类)
C-->>MR : 返回中间件实例
MR->>MR : 若实现 ParameterizedMiddleware,设置路由参数
MR-->>MR : 加入最终中间件链

依赖关系分析

  • bootstrap.php 在引导阶段初始化容器并注册 Request、DelegatingRouter,保证路由调度与请求对象在 Init::boot 之前可用
  • helpers.php 暴露 app() 作为容器语法糖,供各端便捷获取服务
  • Container 内部维护 bindings/singletons/factories/contextualBindings/aliases/buildStack,形成清晰的依赖图
graph LR
B["bootstrap.php"] --> C["Container"]
B --> H["helpers.php(app)"]
C --> M["MiddlewareRegistry"]
H --> C

性能考量

  • 单例缓存:singleton()/instance() 会缓存实例,减少重复构造开销
  • 工厂闭包:每次 make() 都会调用工厂,适合需要动态配置的场景,但注意避免昂贵计算
  • 上下文绑定:仅作用于特定消费者,避免全局替换带来的额外查找成本
  • 反射开销:build() 使用反射解析构造函数,建议在热路径中尽量复用实例或使用工厂优化
  • 构建栈:buildStack 用于上下文绑定解析,保持栈大小最小化,避免深层依赖链导致的性能退化

故障排查指南

  • 无法解析参数:当依赖既无默认值也不可为空且未注册时,会抛出包含构建链信息的异常,便于定位问题
  • 上下文绑定顺序:ensure needs() 先于 give() 调用,否则会抛出逻辑异常
  • 中间件构造异常:中间件构造期异常会向上抛出,防止安全中间件被静默跳过
  • 单例状态污染:测试环境可使用 reset() 清空容器状态,避免跨用例污染

结论

DouPHP 容器提供了完善的扩展点机制,支持绑定、工厂、单例、上下文绑定与别名,满足大多数横切关注点的装饰需求。通过继承 Container 并重写关键方法,可实现更细粒度的控制;结合中间件与引导期注册,能够在不侵入业务代码的前提下增强系统能力。建议在生产环境中谨慎使用工厂与反射,合理运用单例与上下文绑定,以获得更好的性能与可维护性。

附录:扩展示例与测试策略

自定义解析器与装饰器示例

  • 日志记录:通过 factory() 返回包装后的服务实例,在调用前后记录日志
  • 性能监控:在 make() 或工厂闭包中记录耗时,输出性能指标
  • 依赖验证:在 resolveDependencies() 中校验依赖类型或可用性,提前发现配置错误

继承 Container 扩展现有功能

  • 重写 make():在解析前/后插入通用逻辑(如审计、埋点)
  • 重写 build():在反射构造前后执行钩子(如资源预热、清理)
  • 重写 resolveDependencies():在依赖解析阶段注入校验或转换逻辑

安全考虑与最佳实践

  • 向后兼容性:优先使用 factory() 与上下文绑定,避免直接修改现有实现
  • 性能影响:避免在工厂中进行昂贵操作;合理使用单例缓存
  • 安全性:确保中间件构造期异常不被吞掉;对敏感依赖进行白名单校验

测试策略

  • 使用 reset() 清空容器状态,确保用例间隔离
  • 通过 instance() 注入测试替身,验证行为而不依赖真实实现
  • 使用 when()->needs()->give() 为特定消费者注入测试实现,避免全局污染
添加日期:2026-10-05