简介
本文聚焦 DouPHP 容器的“方法调用注入”能力,围绕容器提供的 call() 方法展开,系统说明其基于反射的参数解析、方法级依赖注入流程、上下文覆盖(特别是 __scene)与 FormRequest 场景化请求对象的自动创建机制。文档同时给出在控制器与服务类中的使用方式、错误处理策略以及性能优化建议,帮助开发者在不侵入业务代码的前提下,获得类型安全、可维护性强的参数注入体验。
项目结构
与方法调用注入直接相关的核心位置如下:
- 容器与依赖解析:core/foundation/container/Container.php
- 路由分发器:core/web/routing/Dispatcher.php
- 表单请求基类:core/web/http/FormRequest.php
- 控制器示例:admin/controller/article/ArticleController.php
- 表单请求示例:admin/request/article/ArticleFormRequest.php
graph TB
A["路由分发器<br/>Dispatcher"] --> B["容器<br/>Container"]
B --> C["控制器实例<br/>ArticleController"]
B --> D["服务实例<br/>ArticleService"]
B --> E["表单请求实例<br/>ArticleFormRequest"]
E --> F["验证器<br/>Validator"]
核心组件
- 容器 Container:提供 make() 构造对象与 call() 调用方法并自动注入参数;支持构造函数注入、上下文绑定、单例与工厂。
- 分发器 Dispatcher:负责中间件管道执行、控制器实例化,并以方法名为 scene 调用容器 call()。
- 表单请求 FormRequest:封装校验规则与白名单提取,支持按场景(store/update)差异化校验。
架构总览
下图展示了从路由到控制器方法调用的完整链路,重点体现 __scene 的传递与 FormRequest 的场景化注入。
sequenceDiagram
participant R as "路由分发器"
participant C as "容器"
participant Ctrl as "控制器"
participant FR as "表单请求"
participant V as "验证器"
R->>C : make(控制器FQCN)
C-->>R : 控制器实例
R->>C : call(控制器, 方法名, ["__scene" => 方法名])
C->>C : 反射获取方法参数
C->>C : resolveDependencies(含__scene)
alt 参数为FormRequest子类
C->>C : make(FormRequest, ["scene" => __scene])
C-->>Ctrl : 已注入场景的FormRequest
Ctrl->>FR : validated()
FR->>V : validate(data, rules)
V-->>FR : 通过或抛出异常
FR-->>Ctrl : 白名单数据
else 其他类型依赖
C->>C : make(依赖类)
C-->>Ctrl : 依赖实例
end
Ctrl-->>R : 返回响应
详细组件分析
容器 call() 方法与反射参数解析
- call() 接收实例、方法名与 contextOverrides(如 __scene),通过反射获取方法参数列表,再交由 resolveDependencies() 解析实际参数数组,最后以 invokeArgs 调用目标方法。
- resolveDependencies() 的核心逻辑:
- 优先使用 overrides 中同名键值覆盖(例如 __scene)。
- 读取参数类型提示:
- 若为 FormRequest 子类,则根据 overrides['__scene'] 作为 scene 构造该请求对象。
- 否则递归 make() 解析依赖。
- 若无类型提示:
- 优先使用默认值。
- 允许 null 时注入 null。
- 否则抛出运行时异常,附带构建栈信息便于定位。
flowchart TD
Start(["进入 resolveDependencies"]) --> ForEach["遍历方法参数"]
ForEach --> CheckOverride{"overrides 中存在同名键?"}
CheckOverride -- 是 --> UseOverride["使用覆盖值"] --> Next["下一个参数"]
CheckOverride -- 否 --> GetType["获取参数类型"]
GetType --> IsForm{"是否为 FormRequest 子类?"}
IsForm -- 是 --> MakeFR["make(FormRequest, ['scene' => __scene])"] --> Next
IsForm -- 否 --> IsClass{"是否类型提示为类?"}
IsClass -- 是 --> MakeDep["make(依赖类)"] --> Next
IsClass -- 否 --> HasDefault{"是否有默认值?"}
HasDefault -- 是 --> UseDefault["使用默认值"] --> Next
HasDefault -- 否 --> AllowNull{"是否允许null?"}
AllowNull -- 是 --> UseNull["注入null"] --> Next
AllowNull -- 否 --> Throw["抛出无法解析参数的异常"]
Next --> End(["完成"])
方法级依赖注入工作流程
- 触发点:Dispatcher 在中间件管道内实例化控制器后,调用 container->call($controller, $method, ['__scene' => $method])。
- 工作流:
- 反射解析方法签名,收集所有参数。
- 对每个参数进行类型检测与依赖解析。
- 遇到 FormRequest 子类时,将 __scene 映射为 scene 传入构造,实现场景化请求对象自动创建。
- 其它依赖通过容器 make() 递归解析,支持上下文绑定与工厂。
sequenceDiagram
participant D as "Dispatcher"
participant K as "Container"
participant M as "控制器方法"
participant F as "FormRequest"
D->>K : call(控制器, 方法名, ["__scene" => 方法名])
K->>K : 反射获取方法参数
K->>K : 逐个解析参数
alt 参数类型为FormRequest
K->>K : make(F, ["scene" => __scene])
K-->>M : 注入场景化的F
else 其他依赖
K->>K : make(依赖)
K-->>M : 注入依赖
end
K-->>D : 返回方法结果
__scene 的特殊处理与上下文覆盖
- __scene 由 Dispatcher 统一注入,值为当前控制器方法名(如 store/update)。
- 在 resolveDependencies() 中,__scene 仅用于 FormRequest 子类的场景化构造,不会作为普通参数注入到方法签名。
- 若需要自定义场景映射,可在具体 FormRequest 中重写 normalizeScene() 实现。
与 FormRequest 的集成机制
- 当方法参数声明为 FormRequest 子类时,容器会自动创建该请求对象,并将 __scene 转换为 scene 传入构造。
- 在 validated() 中,会读取 rules() 定义的校验规则,结合 validationData() 提供的数据源进行校验,并返回白名单字段集合。
- 典型用法:控制器方法形参直接声明 ArticleFormRequest,无需手动 new 或传参。
classDiagram
class FormRequest {
+string scene
+__construct(scene)
+rules() array
+validated() array
#validationData() array
#normalizeScene(scene) string
}
class ArticleFormRequest {
+rules() array
#validationData() array
}
FormRequest <|-- ArticleFormRequest
控制器与服务类中的使用方法
- 控制器方法可直接声明依赖类型,包括 Request、FormRequest 子类以及任意服务类。容器会在调用前自动解析并注入。
- 示例路径:
- 控制器方法声明 FormRequest 与 Request:ArticleController::store:127-137、ArticleController::update:182-189
- 控制器构造注入服务:ArticleController::__construct:43-46
实际使用场景与最佳实践
- 场景化校验:在 ArticleFormRequest::rules() 中根据 $this->scene 区分 store/update 的不同必填项与唯一性规则。
- 数据源扩展:在 ArticleFormRequest::validationData() 中合并路由参数(如 update 场景下的 id)到待校验数据。
- 控制器职责清晰:控制器只关注业务编排与响应组装,校验与白名单由 FormRequest 承担。
依赖关系分析
- Dispatcher 依赖 Container 完成控制器实例化与方法调用。
- Container 依赖反射与自身 make() 完成依赖解析,必要时递归构建依赖树。
- FormRequest 依赖 Validator 与语言包、数据库门面进行校验。
- 控制器依赖服务与请求对象,但通过容器注入,降低耦合度。
graph LR
Dispatcher --> Container
Container --> Controller
Container --> Service
Container --> FormRequest
FormRequest --> Validator
性能考虑
- 反射开销:call() 每次调用都会反射方法签名,建议在高频路径上避免过度嵌套的依赖链,或将复杂依赖提升到服务层并通过构造注入复用。
- 单例与工厂:对重量级依赖使用 singleton() 或 factory() 减少重复构建成本。
- 上下文绑定:使用 when()->needs()->give() 针对特定消费者替换实现,避免不必要的依赖解析。
- 表单校验:rules() 尽量精简,仅在必要时引入数据库唯一性检查;大表单可拆分场景以减少规则数量。
故障排查指南
- 无法解析参数异常:当参数既无类型提示、又无默认值且不允许 null 时,容器会抛出运行时异常,包含构建栈信息,便于快速定位缺失依赖。
- 场景不生效:确认 Dispatcher 是否正确注入 __scene;如需自定义场景映射,请在 FormRequest 中重写 normalizeScene()。
- 校验失败:检查 FormRequest::rules() 与 validationData() 是否正确覆盖场景需求;注意 update 场景下 id 的来源与合并。
- 依赖循环:避免控制器与服务之间相互依赖;可通过接口抽象与上下文绑定解耦。
结论
DouPHP 容器的 call() 方法通过反射与依赖解析,实现了方法级依赖注入与场景化请求对象的自动创建。配合 Dispatcher 的 __scene 注入与 FormRequest 的场景化校验,开发者可以在控制器与服务中声明式地表达依赖,获得更清晰、更安全、更易维护的代码结构。合理使用单例、工厂与上下文绑定,可进一步优化性能与可测试性。
附录
- 关键入口参考:
- 容器方法调用与依赖解析:Container.php:304-401
- 分发器注入 __scene:Dispatcher.php:54-58
- 表单请求基类与场景化:FormRequest.php:24-93
- 控制器方法注入示例:ArticleController.php:127-189
- 表单请求规则与数据源:ArticleFormRequest.php:39-96