简介
本文面向开发者,系统化阐述 DouPHP 中“服务提供者”的完整生命周期:加载、注册、初始化与销毁。重点解释 ProviderRegistry 如何管理服务提供者的注册与发现,三端 Init 如何驱动 Provider 启动顺序与依赖解析,以及服务提供者与容器的交互方式(绑定、工厂、单例)。同时给出生命周期钩子与扩展点、事件监听机制,以及调试与监控建议。
项目结构
DouPHP 的服务提供者位于 core/foundation/provider 下,由容器 core/foundation/container 承载绑定与解析;三端入口 front/admin/api 的 Init 类在各自启动流程中调用 ProviderRegistry::registerAll() 完成注册;全局引导 core/bootstrap.php 负责 DI 容器实例化与早期绑定。
graph TB
A["core/bootstrap.php"] --> B["Container 单例"]
B --> C["ProviderRegistry::registerAll()"]
C --> D["LanguageServiceProvider"]
C --> E["PluginServiceProvider"]
C --> F["DataServiceProvider"]
D --> G["语言契约 -> 真实现或 NullLanguageService"]
E --> H["插件契约 -> 真实现或 NullPluginService"]
F --> I["数据契约 -> 真实现或 NullDataService"]
核心组件
- ProviderRegistry:轻量注册总线,维护已启用 Provider 列表,按序调用各 Provider::register(Container)。
- Container:轻量 DI 容器,支持 bind/singleton/factory/instance/alias/when()->needs()->give()、make/call、上下文绑定、构建栈与依赖解析。
- 三个内置 Provider:
- LanguageServiceProvider:将语言相关契约绑定到真实现或 NullLanguageService。
- PluginServiceProvider:将插件查询契约绑定到真实现或 NullPluginService。
- DataServiceProvider:将碎片数据访问契约绑定到真实现或 NullDataService。
架构总览
服务提供者生命周期贯穿“引导期”和“运行时”。引导期由 bootstrap 创建容器并触发 Provider 注册;三端 Init 在 features 就绪后再次注册 Provider,以根据配置选择真实现或降级实现。运行时通过容器 make() 解析契约,Provider 中的工厂闭包决定具体实现。
sequenceDiagram
participant Boot as "引导器"
participant C as "容器"
participant PR as "ProviderRegistry"
participant LSP as "LanguageServiceProvider"
participant PSP as "PluginServiceProvider"
participant DSP as "DataServiceProvider"
Boot->>C : 获取单例
Boot->>PR : registerAll(C)
PR->>LSP : register(C)
PR->>PSP : register(C)
PR->>DSP : register(C)
Note over LSP,DSP : 各 Provider 向容器注册 factory(契约 => 实现)
详细组件分析
ProviderRegistry:注册与发现
- 职责:集中管理 Provider 列表,按固定顺序依次调用静态方法 register(Container)。
- 设计要点:
- 仅暴露 registerAll(),不引入 boot 阶段,保持简单。
- 对 class_exists 做防御性检查,避免未安装模块导致错误。
- 扩展点:新增 Provider 只需加入内部列表并提供静态 register(Container)。
flowchart TD
Start(["进入 registerAll"]) --> Loop{"遍历 Provider 列表"}
Loop --> |存在| Call["调用 Provider::register(Container)"]
Loop --> |不存在| Next["跳过"]
Call --> Next
Next --> End(["结束"])
容器 Container:绑定、单例与依赖解析
- 绑定模式:
- bind:每次解析创建新实例。
- singleton:首次解析后缓存实例。
- factory:用闭包按需创建,覆盖先前 instance/singleton 缓存。
- instance:直接注入已有实例,覆盖 factory。
- alias:别名转发。
- 依赖解析:
- make() 支持反射自动注入构造函数参数。
- when()->needs()->give() 提供上下文绑定,仅在特定消费者生效。
- buildStack 用于定位上下文绑定的直接消费者。
- 状态查询:has/bound/resolved/reset 等便于测试隔离与诊断。
classDiagram
class Container {
+getInstance()
+bind()
+singleton()
+factory()
+instance()
+alias()
+has()
+bound()
+resolved()
+reset()
+make()
+call()
+when()
}
class ContextualBindingBuilder {
+needs()
+give()
}
Container --> ContextualBindingBuilder : "when()"
语言服务提供者:双闸门与降级
- 行为:为 LanguageContract 与 AdminLanguageContract 注册工厂。
- 判断条件:
- features.language 开启且对应实现类存在于磁盘。
- 否则返回 NullLanguageService,保证业务调用面始终可用。
- 时机:
- 引导期先注册一次(features 可能未就绪,落到 Null)。
- 三端 Init 在 features 设置后再重新注册,使 make() 基于最新配置解析到真实现。
flowchart TD
S["语言契约工厂"] --> Check{"features.language 开启?"}
Check --> |否| Null["返回 NullLanguageService"]
Check --> |是| Exists{"实现类存在?"}
Exists --> |否| Null
Exists --> |是| Real["返回 LanguageService / LanguageAdminService"]
插件服务提供者:模块可用性守卫
- 行为:为 PluginServiceContract 注册工厂。
- 判断条件:实现类存在于磁盘(plugin 模块未被卸载)。
- 降级策略:否则返回 NullPluginService,保证 plugin() 调用稳定。
数据服务提供者:可选能力开关
- 行为:为 DataServiceContract 注册工厂。
- 判断条件:features.data 开启且 DataService 类存在。
- 降级策略:否则返回 NullDataService,确保 data() 可解析。
三端 Init 与 Provider 启动顺序
- 前台 Init:
- 先注册 Provider(features 未就绪时落到 Null)。
- 设置 features 后再次注册 Provider,再实例化语言契约。
- 后台 Init:
- 同样两次注册 Provider,并在 loadModules 中根据 features 重新解析语言契约与插件契约。
- API Init:
- 与前台一致的两段式注册,确保 features 生效后使用真实现。
sequenceDiagram
participant F as "前台 Init"
participant A as "后台 Init"
participant P as "API Init"
participant C as "容器"
participant R as "ProviderRegistry"
F->>R : registerAll(C) // 早期注册
A->>R : registerAll(C) // 早期注册
P->>R : registerAll(C) // 早期注册
F->>F : 设置 features
A->>A : 设置 features
P->>P : 设置 features
F->>R : registerAll(C) // 二次注册
A->>R : registerAll(C) // 二次注册
P->>R : registerAll(C) // 二次注册
生命周期钩子与扩展点
- 引导期钩子:
- bootstrap 中创建容器并注册路由与请求单例,随后调用 ProviderRegistry::registerAll()。
- InitTrait 提供日志、安全、会话、核心对象等通用步骤,供三端复用。
- 运行时扩展点:
- 通过 Provider::register(Container) 注入新的契约绑定。
- 使用 Container::when()->needs()->give() 进行上下文绑定,针对特定消费者切换实现。
- 场景分发器 SceneRegistry 支持延迟注册与首次分发时执行,适合扩展点登记。
生命周期事件监听与处理
- 事件系统:Event::fire()/dispatch() 支持按事件名注册监听器并按优先级执行。
- 邮件通知:MailNotificationRegistrar 在引导期注册可选邮件事件监听。
- 使用建议:
- 在 Provider 注册阶段订阅关键事件(如模块加载、配置变更)。
- 通过优先级控制监听器执行顺序。
依赖关系分析
- ProviderRegistry 依赖 Container,并通过静态方法调用各 Provider。
- 各 Provider 依赖 Config 与具体服务类,决定是否返回真实现或 Null 占位。
- 三端 Init 依赖 ProviderRegistry、Container、Config、SystemBootstrap/SiteBootstrap,协调 features 与 Provider 注册时机。
- 容器依赖反射与上下文绑定机制,支撑复杂依赖链解析。
graph LR
PR["ProviderRegistry"] --> C["Container"]
PR --> LSP["LanguageServiceProvider"]
PR --> PSP["PluginServiceProvider"]
PR --> DSP["DataServiceProvider"]
LSP --> CFG["Config"]
PSP --> CFG
DSP --> CFG
FrontInit["前台 Init"] --> PR
AdminInit["后台 Init"] --> PR
ApiInit["API Init"] --> PR
性能考量
- 工厂闭包惰性创建:Provider 中的 factory 仅在 make() 时执行,减少启动开销。
- 单例缓存:singleton/instance 避免重复构造昂贵对象。
- 降级策略:Null*Service 保证最小可用路径,避免缺失模块导致的异常分支。
- 二次注册时机:在 features 设置后重新注册 Provider,避免不必要的重试与解析。
故障排查指南
- 常见问题定位:
- 语言/插件/数据服务不可用:检查 features.* 配置与对应实现类是否存在。
- 容器解析失败:查看 make() 抛出的依赖解析错误信息,确认是否缺少绑定或上下文绑定。
- 启动顺序问题:确认 Init 中 ProviderRegistry::registerAll() 是否在 features 设置后被调用。
- 调试建议:
- 使用 Container::has()/bound()/resolved() 检查绑定与实例状态。
- 在 Provider 中记录工厂执行路径与返回值,辅助定位降级逻辑。
- 利用 InitTrait 的全局异常与运行期错误处理,收集堆栈与上下文。
结论
DouPHP 的服务提供者生命周期围绕“引导期注册 + 运行时解析”展开。ProviderRegistry 提供统一注册入口,Container 提供灵活的绑定与依赖解析能力,三端 Init 协调 features 与 Provider 的启动顺序。通过 Null*Service 降级与工厂惰性创建,系统在功能可选与稳定性之间取得平衡。开发者可通过 Provider、事件系统与上下文绑定扩展平台能力,并使用容器状态查询与日志工具进行调试与监控。
附录
- 最佳实践:
- 新增 Provider 时遵循“配置开关 + 类存在性”双重判断,确保降级路径。
- 优先使用 factory 而非 instance,以便在 features 变化后重新解析。
- 使用 when()->needs()->give() 限定上下文绑定范围,避免污染全局解析。
- 参考路径:
- 引导期容器与助手加载:core/bootstrap.php
- 三端 Init 启动流程:front/admin/api 下的 Init.php
- 事件系统:core/foundation/event/Event.php