文档目录
服务提供者

简介

本文件面向模块开发者,系统化说明 DouPHP 的“服务提供者”机制及其在依赖注入容器中的作用。内容涵盖:

  • 服务提供者的概念、职责与生命周期(从加载到初始化)
  • 服务注册与发现机制(静态注册总线 + 插件自动发现)
  • 事件监听与场景分发(基于场景的事件系统)
  • 自定义服务提供者的开发指南(接口、实现类、配置文件)
  • 提供者之间的依赖关系与加载顺序控制
  • 测试策略与调试技巧
  • 实际使用示例与最佳实践

项目结构

DouPHP 的服务提供者体系由以下关键部分组成:

  • 服务提供者注册总线:集中管理基础能力 Provider 的注册顺序
  • 具体 Provider:将抽象契约绑定到具体实现或降级实现
  • 依赖注入容器:提供绑定、工厂、单例、上下文绑定与自动构造注入
  • 事件系统:按场景注册并分发处理器,支持延迟引导
  • 插件注册中心:扫描 manifest.php 自动发现第三方登录 Provider
graph TB
A["ProviderRegistry<br/>注册总线"] --> B["LanguageServiceProvider<br/>语言服务"]
A --> C["PluginServiceProvider<br/>插件服务"]
A --> D["DataServiceProvider<br/>数据服务"]
E["Container<br/>依赖注入容器"] --> B
E --> C
E --> D
F["SceneRegistry<br/>场景事件分发"] --> G["业务处理器"]
H["ConnectPluginRegistry<br/>插件自动发现"] --> I["第三方登录 Provider"]

核心组件

  • 服务提供者注册总线(ProviderRegistry)
    • 维护一个固定的 Provider 列表,按顺序调用各 Provider 的静态 register(Container) 方法完成绑定
    • 优点:简单、可预测;缺点:新增 Provider 需修改注册表
  • 具体服务提供者
    • LanguageServiceProvider:根据 features.language 与类存在性,绑定语言相关契约到真实实现或空实现
    • PluginServiceProvider:根据 plugin 模块是否可用,绑定插件查询契约到真实实现或空实现
    • DataServiceProvider:根据 features.data 与类存在性,绑定数据访问契约到真实实现或空实现
  • 依赖注入容器(Container)
    • 提供 bind/singleton/factory/instance/alias/when()->needs()->give() 等能力
    • 支持构造函数自动注入、上下文绑定、状态查询与重置
  • 事件系统(SceneRegistry)
    • 通过 addBootstrapper 登记注册器类名,首次 dispatch 时执行其 register() 贡献处理器
    • 支持按场景与路由键进行精确或扇出式分发
  • 插件自动发现(ConnectPluginRegistry)
    • 扫描 PLUGIN_PATH 下每个插件的 manifest.php,校验并解析 provider 类名,实例化后缓存

架构总览

下图展示了服务提供者从启动到可用的整体流程,包括容器绑定、事件注册与插件发现。

sequenceDiagram
participant Boot as "启动阶段"
participant Reg as "ProviderRegistry"
participant LSP as "LanguageServiceProvider"
participant PSP as "PluginServiceProvider"
participant DSP as "DataServiceProvider"
participant C as "Container"
participant SR as "SceneRegistry"
participant CPR as "ConnectPluginRegistry"
Boot->>Reg : 调用 registerAll(container)
Reg->>LSP : register(container)
LSP->>C : factory(语言契约, 闭包)
Reg->>PSP : register(container)
PSP->>C : factory(插件契约, 闭包)
Reg->>DSP : register(container)
DSP->>C : factory(数据契约, 闭包)
Note over C : 契约绑定完成,按需解析真实或空实现
Boot->>SR : addBootstrapper(注册器类)
Boot->>CPR : 构造并 discover()
CPR->>CPR : 扫描 manifest.php 并解析 provider
CPR->>C : make(provider类)
Note over CPR,C : 第三方登录 Provider 已就绪

详细组件分析

服务提供者注册总线(ProviderRegistry)

  • 作用:集中声明并顺序执行各 Provider 的静态 register(Container) 方法
  • 设计要点:
    • 固定数组维护 Provider 列表,保证加载顺序可控
    • 若某 Provider 类不存在则跳过,增强健壮性
  • 扩展方式:新增 Provider 需在注册表中添加类名
flowchart TD
Start(["进入 registerAll"]) --> Loop{"遍历 Provider 列表"}
Loop --> |存在| Call["调用 Provider::register(container)"]
Loop --> |不存在| Next["跳过"]
Call --> Next
Next --> End(["结束"])

语言能力提供者(LanguageServiceProvider)

  • 作用:将语言相关契约绑定到真实实现或空实现,确保调用面始终可用
  • 判断逻辑:
    • 主闸门:features.language 配置开关
    • 次闸门:对应实现类是否存在于磁盘
  • 行为:满足条件则返回真实服务,否则返回空实现
flowchart TD
S(["进入 register"]) --> CheckLang{"features.language 开启?"}
CheckLang --> |否| NullLang["绑定空实现"]
CheckLang --> |是| ClassExists{"实现类存在?"}
ClassExists --> |否| NullLang
ClassExists --> |是| RealLang["绑定真实实现"]
NullLang --> End(["完成"])
RealLang --> End

插件查询提供者(PluginServiceProvider)

  • 作用:将插件查询契约绑定到真实实现或空实现
  • 判断逻辑:仅检查实现类是否存在于磁盘(卸载/未升级场景)
  • 行为:满足条件返回真实服务,否则返回空实现

数据访问提供者(DataServiceProvider)

  • 作用:将数据访问契约绑定到真实实现或空实现
  • 判断逻辑:features.data 开启且实现类存在
  • 行为:满足条件返回 DataService,否则返回 NullDataService

依赖注入容器(Container)

  • 能力概览:
    • 绑定与别名:bind/alias
    • 单例与实例:singleton/instance
    • 工厂闭包:factory(覆盖先前绑定/单例缓存)
    • 上下文绑定:when()->needs()->give()
    • 自动构造注入:make/build/resolveDependencies
    • 状态查询:has/bound/resolved/reset
  • 典型用法:
    • Provider 通过 container->factory(...) 注册契约到实现的映射
    • 运行时按需解析契约,自动注入依赖
classDiagram
class Container {
+bind(abstract, concrete)
+singleton(abstract, concrete)
+factory(abstract, factory)
+instance(abstract, instance)
+alias(alias, target)
+make(abstract, parameters)
+when(consumer) ContextualBindingBuilder
+has(abstract) bool
+bound(abstract) bool
+resolved(abstract) bool
+reset()
}
class ContextualBindingBuilder {
+needs(abstract) $this
+give(concrete) void
}
Container --> ContextualBindingBuilder : "when() 返回"

事件系统(SceneRegistry)

  • 作用:按场景注册并分发处理器,支持延迟引导
  • 关键点:
    • addBootstrapper 登记注册器类名,首次 dispatch 时统一执行其 register()
    • dispatch 支持按 key 精确触发或扇出所有处理器
    • ensureBooted 保证注册器只执行一次
sequenceDiagram
participant App as "应用"
participant SR as "SceneRegistry"
participant BS as "注册器类"
participant H as "处理器"
App->>SR : addBootstrapper(BS)
App->>SR : dispatch(scene, payload, key)
SR->>SR : ensureBooted()
SR->>BS : register()
SR->>H : handle(scene, payload)

插件自动发现(ConnectPluginRegistry)

  • 作用:扫描插件目录下的 manifest.php,解析并实例化第三方登录 Provider
  • 流程:
    • 读取 PLUGIN_PATH 下每个插件目录的 manifest.php
    • 校验 manifest 结构并提取 provider 类名
    • 通过容器实例化并缓存实例
    • 提供 has()/provider() 查询接口
flowchart TD
Start(["构造 ConnectPluginRegistry"]) --> Discover["discover(): 扫描插件目录"]
Discover --> Manifest{"存在 manifest.php?"}
Manifest --> |否| NextDir["下一个目录"]
Manifest --> |是| Validate["ManifestValidator 校验并提取 provider"]
Validate --> Exists{"类存在?"}
Exists --> |否| NextDir
Exists --> |是| Make["container->make(provider)"]
Make --> Instance{"实现接口?"}
Instance --> |否| NextDir
Instance --> |是| Cache["缓存 providerClassMap 与实例"]
Cache --> NextDir
NextDir --> End(["完成"])

支付提供者示例(AlipayProvider)

  • 作用:实现支付相关接口,封装支付宝服务
  • 特点:
    • 通过构造函数注入 AlipayService
    • 暴露 pluginId/meta/start/notify/finish/query 等方法
    • 作为插件生态中的 Provider 示例,体现接口契约与实现分离

依赖关系分析

  • ProviderRegistry 依赖 Container,并通过静态方法依次调用各 Provider 的 register(Container)
  • 各 Provider 依赖 Container 的 factory 方法,将契约绑定到具体实现或空实现
  • SceneRegistry 与业务处理器之间通过工厂函数解耦,避免强耦合
  • ConnectPluginRegistry 依赖 Container 与 ManifestValidator,完成插件 Provider 的发现与实例化
graph LR
PR["ProviderRegistry"] --> C["Container"]
PR --> LSP["LanguageServiceProvider"]
PR --> PSP["PluginServiceProvider"]
PR --> DSP["DataServiceProvider"]
LSP --> C
PSP --> C
DSP --> C
SR["SceneRegistry"] --> H["业务处理器"]
CPR["ConnectPluginRegistry"] --> C
CPR --> MV["ManifestValidator"]

性能考量

  • 懒加载与一次性引导:
    • SceneRegistry 的注册器仅在首次 dispatch 时执行,避免启动期开销
    • 插件 Provider 通过容器实例化并缓存,减少重复构建成本
  • 降级策略:
    • 当模块未启用或类不存在时,返回空实现,避免运行时异常与额外分支判断
  • 容器优化:
    • 工厂闭包覆盖单例缓存,确保最新绑定生效
    • 上下文绑定仅作用于当前消费者,降低全局污染风险

故障排查指南

  • 常见问题定位:
    • 契约解析失败:检查 Container 中是否正确注册了 factory/bind/singleton
    • 模块未启用:确认 features.* 配置与实现类是否存在
    • 插件未生效:检查 manifest.php 结构与 provider 类名是否符合约束
  • 调试建议:
    • 使用 Container::has/bound/resolved 验证绑定状态
    • 使用 Container::reset 隔离测试环境
    • 对 SceneRegistry 使用 reset 清理状态,确保测试幂等
  • 错误形态:
    • 无法解析参数:容器会抛出明确错误信息,包含依赖链上下文

结论

DouPHP 的服务提供者体系以“注册总线 + 契约绑定 + 容器解析”为核心,结合“场景事件分发”和“插件自动发现”,实现了高内聚、低耦合的模块化架构。通过明确的加载顺序、降级策略与懒引导机制,系统在可扩展性与稳定性之间取得平衡。模块开发者可据此快速扩展能力,同时保持系统的可维护性与可测试性。

附录

自定义服务提供者开发指南

  • 步骤概览:
    1. 定义契约接口(如 XxxContract)
    2. 实现具体服务类(如 XxxService)
    3. 编写服务提供者类(XxxServiceProvider),实现静态 register(Container) 方法
    4. 在 ProviderRegistry 中添加该 Provider 类名,控制加载顺序
    5. 在业务代码中通过容器解析契约,获得所需实现
  • 配置文件:
    • 通过 features.* 开关控制模块可用性
    • 插件模块可通过 manifest.php 声明 provider 类名与元数据
  • 最佳实践:
    • 提供空实现作为降级方案,确保调用面稳定
    • 使用工厂闭包进行条件绑定,避免硬编码
    • 利用上下文绑定为特定消费者替换实现

服务提供者之间的依赖关系与加载顺序控制

  • 顺序控制:
    • 通过 ProviderRegistry 中 Provider 列表的顺序控制加载先后
    • 若 Provider A 依赖 Provider B 的绑定结果,应将 B 置于 A 之前
  • 依赖声明:
    • 通过 Container 的 when()->needs()->give() 为特定消费者指定实现
    • 使用 singleton/instance 控制实例生命周期
  • 注意事项:
    • 避免循环依赖
    • 在 Provider 中尽量只做绑定,不做复杂初始化逻辑

测试策略与调试技巧

  • 测试策略:
    • 使用 Container::reset 清理绑定状态,确保测试隔离
    • 使用 SceneRegistry::reset 清理事件注册,避免跨测试干扰
    • 针对 Provider 的条件分支,分别测试启用与禁用场景
  • 调试技巧:
    • 打印 Container::has/bound/resolved 结果,验证绑定状态
    • 对 SceneRegistry 的分发过程,逐步断点观察 ensureBooted 的执行时机
    • 对插件自动发现,检查 manifest.php 结构与 provider 类名

实际项目中的使用示例与最佳实践

  • 示例一:语言服务
    • 在 LanguageServiceProvider 中根据 features.language 与类存在性绑定契约
    • 业务代码通过容器解析语言契约,无需关心实现细节
  • 示例二:插件查询
    • 在 PluginServiceProvider 中根据模块可用性绑定契约
    • 业务代码调用插件查询接口,内部自动选择真实或空实现
  • 示例三:支付插件
    • 通过 AlipayProvider 实现支付接口,封装外部服务调用
    • 使用 manifest.php 声明插件元数据与 provider 类名
添加日期:2026-10-05