文档目录
服务注册机制

简介

本文件面向DouPHP的“服务提供者”机制,系统性说明服务如何被注册、发现、解析与使用。重点覆盖以下主题:

  • 服务提供者的注册流程与发现机制
  • DataServiceProvider、LanguageServiceProvider、PluginServiceProvider 的注册逻辑
  • 服务绑定方式与优先级控制(bind/singleton/factory/instance/alias)
  • 服务别名与接口映射关系
  • 服务注册的配置选项与自定义注册方法
  • 服务冲突解决与版本兼容性处理
  • 服务注册的性能优化策略与缓存机制
  • 为模块开发者提供的自定义服务注册完整指南

项目结构

DouPHP 的服务注册围绕“容器 + 提供者 + 引导阶段”展开:

  • 容器:DI 容器负责绑定、单例、工厂、别名、上下文绑定与自动依赖注入
  • 提供者:按能力维度拆分(语言、数据、插件),在引导期统一注册到容器
  • 引导阶段:系统启动时先加载基础常量与容器,再装配站点配置与功能开关,最后调用 ProviderRegistry 一次性注册所有 Provider
graph TB
A["引导入口<br/>core/bootstrap.php"] --> B["初始化容器<br/>Container::getInstance()"]
B --> C["站点配置装配<br/>SiteBootstrap::loadSite()"]
C --> D["功能开关装配<br/>SystemBootstrap::loadCore()"]
D --> E["前端引导<br/>front/init/Init.php"]
E --> F["ProviderRegistry::registerAll()<br/>注册语言/数据/插件服务"]
F --> G["容器解析契约<br/>make(LanguageContract/...)<br/>返回真实现或Null占位"]

核心组件

  • 容器 Container:提供 bind、singleton、factory、instance、alias、when()->needs()->give()、make()、call()、reset() 等能力;支持别名转发、工厂覆盖、单例缓存、上下文绑定、反射自动注入
  • 提供者注册器 ProviderRegistry:集中维护平台能力 Provider 列表,并在引导期统一调用 register(Container)
  • 内置提供者:
    • LanguageServiceProvider:将语言相关契约绑定到真实现或 Null 占位
    • DataServiceProvider:将数据访问契约绑定到真实现或 Null 占位
    • PluginServiceProvider:将插件查询契约绑定到真实现或 Null 占位

架构总览

下图展示了从引导到服务解析的关键路径:引导阶段装配配置与功能开关后,ProviderRegistry 统一注册各 Provider;业务通过容器 make() 解析契约,得到真实现或 Null 占位。

sequenceDiagram
participant Boot as "引导阶段"
participant Reg as "ProviderRegistry"
participant LSP as "LanguageServiceProvider"
participant DSP as "DataServiceProvider"
participant PSP as "PluginServiceProvider"
participant C as "Container"
participant App as "业务代码"
Boot->>C : getInstance()
Boot->>Boot : loadSite()/loadCore()
Boot->>Reg : registerAll(C)
Reg->>LSP : register(C)
Reg->>DSP : register(C)
Reg->>PSP : register(C)
App->>C : make(LanguageContract/...)
C-->>App : 真实现或Null占位

详细组件分析

容器 Container:绑定、解析与优先级

  • 绑定方式
    • bind:每次解析创建新实例
    • singleton:首次解析后缓存实例
    • factory:以闭包动态创建实例,可覆盖先前 instance/单例缓存
    • instance:直接注入已有实例,可覆盖先前 factory
    • alias:别名转发到目标抽象
  • 解析顺序(make)
    • 别名转发
    • 工厂优先
    • 单例缓存命中则返回
    • 解析具体类并反射构造,必要时递归解析依赖
    • 单例结果缓存
  • 上下文绑定
    • when(消费者)->needs(抽象)->give(具体/闭包),仅对当前消费者生效
  • 状态管理
    • has/bound/resolved 查询
    • reset 清空全部绑定/单例/工厂/上下文/别名/构建栈,便于测试隔离
flowchart TD
Start(["make(abstract)"]) --> Alias{"是否别名?"}
Alias --> |是| ResolveAlias["转发到目标抽象"]
Alias --> |否| Factory{"是否工厂?"}
ResolveAlias --> Factory
Factory --> |是| CallFactory["调用工厂闭包"]
Factory --> |否| Singleton{"是否已缓存单例?"}
Singleton --> |是| ReturnCached["返回缓存实例"]
Singleton --> |否| Build["反射构建并解析依赖"]
Build --> SaveSingleton{"是否单例?"}
SaveSingleton --> |是| Cache["写入缓存"]
SaveSingleton --> |否| End(["返回实例"])
CallFactory --> End
Cache --> End

提供者注册器 ProviderRegistry:统一注册总线

  • 职责:集中维护平台能力 Provider 列表,依次调用其静态 register(Container)
  • 特点:轻量、无 boot 阶段概念,调用语义简单;仅在 Config 与 features 就绪后由 Init 调用

语言服务提供者 LanguageServiceProvider

  • 绑定契约
    • LanguageContract → LanguageService 或 NullLanguageService
    • AdminLanguageContract → LanguageAdminService 或 NullLanguageService
  • 条件判断
    • 检查 features.language 开关
    • 检查对应实现类是否存在于磁盘
    • 任一不满足即返回 Null 占位,保证调用面稳定
  • 引导时序
    • 早期可能先 instance() 锁住 Null 占位
    • 待 features.language 生效后,再次 ProviderRegistry::registerAll() 重新注册 factory,然后 instance() 锁定最新解析结果
flowchart TD
S(["register(container)"]) --> CheckLang{"features.language 开启?"}
CheckLang --> |否| UseNull["绑定到 NullLanguageService"]
CheckLang --> |是| ClassExist{"实现类存在?"}
ClassExist --> |否| UseNull
ClassExist --> |是| BindReal["绑定到 LanguageService/AdminLanguageService"]

数据服务提供者 DataServiceProvider

  • 绑定契约
    • DataServiceContract → DataService 或 NullDataService
  • 条件判断
    • 检查 features.data 开关
    • 检查 DataService 实现类是否存在于磁盘
    • 任一不满足即返回 Null 占位
  • 设计要点
    • 允许重复调用覆盖工厂闭包
    • 保证 data() 始终可解析,避免业务侧分支判断

插件服务提供者 PluginServiceProvider

  • 绑定契约
    • PluginServiceContract → PluginService 或 NullPluginService
  • 条件判断
    • 检查插件实现类是否存在于磁盘
    • 未卸载则绑定真实现,否则返回 Null 占位
  • 设计要点
    • 细粒度可用性判断保留在真实现内部各方法的 isAvailable() 守卫中

引导阶段与配置装配

  • bootstrap 阶段
    • 定义根路径、配置路径、存储路径
    • 注册自动加载与门面短名
    • 初始化容器并提前绑定路由与 Request 单例
  • SystemBootstrap
    • 读取模块设置、系统常量、语言清单、功能开关
  • SiteBootstrap
    • 装配站点配置与参数,写入 Config
  • front/init/Init
    • 在 features 就绪后调用 ProviderRegistry::registerAll()
    • 重新解析语言契约并锁定实例
sequenceDiagram
participant BS as "bootstrap.php"
participant SB as "SystemBootstrap"
participant SS as "SiteBootstrap"
participant INIT as "front/init/Init.php"
participant REG as "ProviderRegistry"
BS->>BS : 初始化容器/请求/路由
BS->>SB : loadCore(options)
SB-->>BS : module/system/features/lang
BS->>SS : loadSite(public, rootUrl)
SS-->>BS : site config
BS->>INIT : bootCore()
INIT->>REG : registerAll(container)
INIT->>INIT : 重新解析语言契约并锁定实例

依赖关系分析

  • ProviderRegistry 依赖 Container,并通过静态方法依次调用各 Provider::register
  • 各 Provider 依赖 Container 与配置/运行时信息(如 features.*)
  • 业务代码通过 Container::make() 解析契约,解耦具体实现
  • 引导阶段确保配置与功能开关在 Provider 注册前就绪
graph LR
PR["ProviderRegistry"] --> C["Container"]
PR --> LSP["LanguageServiceProvider"]
PR --> DSP["DataServiceProvider"]
PR --> PSP["PluginServiceProvider"]
LSP --> C
DSP --> C
PSP --> C
App["业务代码"] --> C

性能与缓存

  • 工厂与单例
    • factory:按需创建,适合复杂初始化或条件选择实现
    • singleton:首次解析后缓存,减少重复开销
    • instance:直接注入已有实例,避免重复构建
  • 别名与上下文绑定
    • alias:快速转发,减少查找成本
    • contextual binding:针对特定消费者切换实现,避免全局覆盖
  • 引导阶段优化
    • 在 features 就绪后再注册 Provider,避免多次重算
    • 语言契约在 features 生效后重新解析并锁定实例,减少后续 make 开销
  • 重置与测试隔离
    • Container::reset() 清空绑定/单例/工厂/上下文/别名/构建栈,便于测试与重启场景

故障排查

  • 语言服务不可用
    • 检查 features.language 是否开启
    • 检查语言实现类是否存在于磁盘
    • 确认引导阶段是否正确执行 ProviderRegistry::registerAll() 并重新锁定实例
  • 数据服务不可用
    • 检查 features.data 是否开启
    • 检查 DataService 实现类是否存在于磁盘
  • 插件服务不可用
    • 检查插件实现类是否存在于磁盘
    • 若真实现内部有 isAvailable() 守卫,需进一步检查运行期条件
  • 容器解析失败
    • 检查是否有正确的绑定/工厂/单例/别名
    • 检查上下文绑定是否覆盖了期望的实现
    • 使用 Container::has()/bound()/resolved() 辅助诊断

结论

DouPHP 的服务注册机制以“容器 + 提供者 + 引导阶段”为核心,通过 ProviderRegistry 统一管理平台能力提供者,结合 Container 的绑定、工厂、单例、别名与上下文绑定能力,实现了高内聚、低耦合的服务发现与解析。内置提供者通过“功能开关 + 类存在性”双闸门,确保在不同部署与升级场景下仍能提供稳定的调用面。引导阶段的配置装配与二次解析保证了服务在正确时机以最优方式注入。

附录:模块开发者自定义指南

  • 何时注册
    • 在 features 与站点配置就绪后,调用 ProviderRegistry::registerAll() 或在 Init 中追加自定义 Provider
  • 如何注册
    • 实现 Provider 类并提供静态 register(Container) 方法
    • 使用 Container::factory() 或 Container::singleton()/instance() 绑定契约到实现
    • 如需别名,使用 Container::alias() 建立别名映射
  • 优先级控制
    • factory 会覆盖先前 instance/单例缓存;instance 会覆盖先前 factory
    • 通过先后顺序控制最终绑定的实现
  • 版本兼容与降级
    • 参考内置提供者的“双闸门”模式:先检查功能开关,再检查实现类是否存在;任一不满足则返回 Null 占位
  • 上下文绑定
    • 使用 when(消费者)->needs(抽象)->give(具体/闭包) 为特定消费者切换实现
  • 示例步骤
    • 定义契约与实现类
    • 编写 Provider::register(Container)
    • 在引导阶段调用 ProviderRegistry::registerAll() 或直接在 Init 中注册
    • 通过 Container::make() 解析契约获取实现
添加日期:2026-10-05