文档目录
方法调用注入

简介

本文聚焦 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
添加日期:2026-10-05