简介
本技术文档围绕 DouPHP 的插件架构进行系统化说明,重点覆盖以下方面:
- 插件生命周期管理、钩子机制与事件总线的设计思路
- 依赖注入容器集成与 Provider 注册机制
- 插件注册、加载顺序、初始化流程
- 插件间通信协议、数据共享机制
- 安全隔离、权限控制与资源访问限制
- 版本兼容性管理、热更新与动态加载卸载能力
- 架构图与流程图,帮助开发者理解工作原理与设计模式
更新 本次更新重点反映了插件架构向新的Provider/Service模式的演进,特别是第三方登录插件的统一架构设计。
项目结构
DouPHP 将"插件"作为可插拔的能力单元,通过统一的 Provider 契约暴露能力,由框架容器在启动时按需发现并装配。典型结构如下:
- 插件目录 plugin/<插件名>/:包含 manifest.php、Provider 类、Service 类等
- 核心服务 core/service/plugin/*:提供插件查询与管理能力
- 容器与提供者 core/foundation/provider/*:负责绑定接口到实现
- 路由与分发 core/web/routing/*:统一请求分发,便于在中间件或控制器中调用插件能力
- 第三方登录插件采用统一的 ConnectPluginProviderInterface 契约
graph TB
A["应用入口"] --> B["容器初始化"]
B --> C["插件服务提供者<br/>PluginServiceProvider"]
C --> D["插件查询接口<br/>PluginServiceContract"]
D --> E["真实现: PluginService"]
D --> F["空实现: NullPluginService"]
G["业务模块"] --> D
H["插件提供者<br/>ExpressProvider / AlipayProvider"] --> I["插件服务层"]
I --> J["外部系统/第三方SDK"]
K["第三方登录插件<br/>AmazonProvider / WxloginProvider"] --> L["连接服务层"]
L --> M["OAuth授权服务"]
图表来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
章节来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
核心组件
- 插件服务提供者(PluginServiceProvider):在容器中注册插件查询能力的工厂,根据运行环境决定返回真实实现或空实现,保证调用面稳定。
- 空实现(NullPluginService):当插件模块不可用时,提供无害默认值,避免 PHP 错误与数据库访问失败。
- 插件提供者(如 ExpressProvider、AlipayProvider):实现领域契约,暴露元数据、配置项与方法集,供上层按策略选择。
- 异步驱动接口(AsyncDriverInterface):定义异步任务提交与轮询的统一契约,便于扩展不同供应商。
- 新增 第三方登录契约接口(ConnectPluginProviderInterface):统一第三方登录插件的标准接口规范。
章节来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
- ExpressProvider.php:11-70
- AlipayProvider.php:15-110
- AsyncDriverInterface.php:21-51
- ConnectPluginProviderInterface.php:27-58
架构总览
下图展示从容器到插件提供者的装配路径,以及业务侧如何通过统一接口获取插件能力。
sequenceDiagram
participant App as "应用"
participant Container as "容器"
participant Psp as "PluginServiceProvider"
participant PS as "PluginServiceContract"
participant Impl as "PluginService(真实现)"
participant NPS as "NullPluginService"
participant Prov as "插件提供者"
participant ConnProv as "第三方登录Provider"
App->>Container : 解析 "PluginServiceContract"
Container->>Psp : 调用工厂注册
alt 插件模块可用
Psp-->>Container : 返回 Impl
Container-->>App : 返回 Impl
App->>Impl : 查询分组/启用状态/默认值
else 插件模块不可用
Psp-->>Container : 返回 NPS
Container-->>App : 返回 NPS
App->>NPS : 查询分组/启用状态/默认值
end
App->>Prov : 按分组/策略选择具体 Provider
App->>ConnProv : 使用统一登录契约
图表来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
章节来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
详细组件分析
插件服务提供者与空实现
- 职责
- 在容器中注册插件查询能力的工厂,依据"插件模块是否就绪"决定返回真实实现或空实现。
- 空实现提供稳定的默认行为,确保业务代码无需关心插件模块是否存在。
- 关键点
- 使用 class_exists 判断插件服务类是否仍在磁盘,从而决定是否启用真实实现。
- 空实现将所有查询方法返回无害默认值,避免运行时异常。
classDiagram
class PluginServiceProvider {
+register(container) void
-pluginModuleReady() bool
}
class PluginServiceContract
class PluginService {
+isAvailable() bool
+hasGroup(group) bool
+valueByGroup(group, field) mixed
+valueBySlug(slug, field) mixed
+getBySlug(slug) array
+existsBySlug(slug) bool
+getWithConfig(slug) array
+defaultPaymentSlug() string
+hasConnect() bool
}
class NullPluginService {
+isAvailable() bool
+hasGroup(group) bool
+valueByGroup(group, field) mixed
+valueBySlug(slug, field) mixed
+getBySlug(slug) array
+existsBySlug(slug) bool
+getWithConfig(slug) array
+defaultPaymentSlug() string
+hasConnect() bool
}
PluginServiceProvider --> PluginService : "条件返回"
PluginServiceProvider --> NullPluginService : "降级返回"
PluginService <|.. PluginServiceContract
NullPluginService <|.. PluginServiceContract
图表来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
章节来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
插件提供者:快递与支付
- 快递提供者(ExpressProvider)
- 实现配送领域契约,暴露插件 ID、元数据(名称、描述、版本、分组、允许客户端)、配置项与方法集。
- 通过 Service 层封装具体计算逻辑。
- 支付宝提供者(AlipayProvider)
- 实现可对账支付契约,暴露 start/notify/finish/query 等生命周期方法。
- 通过 Service 层处理签名、回调与查询。
classDiagram
class ShippingPluginProviderInterface
class ReconcilablePaymentProviderInterface
class ExpressProvider {
+pluginId() string
+meta() array
+methods() array
}
class AlipayProvider {
+pluginId() string
+meta() array
+start(request) string
+notify(payload) string
+finish(payload) string
+query(request) PaymentQueryResult
}
ExpressProvider ..|> ShippingPluginProviderInterface
AlipayProvider ..|> ReconcilablePaymentProviderInterface
图表来源
- ExpressProvider.php:11-70
- AlipayProvider.php:15-110
章节来源
- ExpressProvider.php:11-70
- AlipayProvider.php:15-110
第三方登录插件统一架构
新增 第三方登录插件现在采用统一的Provider/Service架构,替代了旧的分离式文件结构。
-
Amazon登录插件
- AmazonProvider:实现 ConnectPluginProviderInterface 接口,提供插件ID、元数据和标准方法
- AmazonService:处理具体的OAuth授权码流程,包括授权跳转、令牌交换、用户资料获取
- 支持标准的OAuth2授权码流程,与微信/QQ登录保持一致的架构模式
-
微信登录插件
- WxloginProvider:实现统一的登录契约接口
- WxloginService:处理微信开放平台和公众号两套appid的智能选择逻辑
- 支持PC扫码登录和微信公众号内自动登录两种模式
classDiagram
class ConnectPluginProviderInterface {
+pluginId() string
+meta() array
+start(request) string
+finish(payload) string
}
class AmazonProvider {
+pluginId() string
+meta() array
+start(request) string
+finish(payload) string
}
class AmazonService {
+start(request) string
+finish(payload) string
-exchangeCode(code, clientId, clientSecret) array
-fetchProfile(accessToken) array
}
class WxloginProvider {
+pluginId() string
+meta() array
+start(request) string
+finish(payload) string
}
class WxloginService {
+start(request) string
+finish(payload) string
-resolveAppCredentials() array
-fetchTokenOpenid(appid, appsecret, code) array
-fetchUserinfo(tokenInfo) array
}
AmazonProvider ..|> ConnectPluginProviderInterface
WxloginProvider ..|> ConnectPluginProviderInterface
AmazonProvider --> AmazonService
WxloginProvider --> WxloginService
图表来源
- ConnectPluginProviderInterface.php:27-58
- AmazonProvider.php:16-82
- AmazonService.php:25-220
- WxloginProvider.php:16-95
- WxloginService.php:21-209
章节来源
- ConnectPluginProviderInterface.php:27-58
- AmazonProvider.php:16-82
- AmazonService.php:25-220
- WxloginProvider.php:16-95
- WxloginService.php:21-209
第三方登录DTO定义
新增 第三方登录插件使用标准化的数据传输对象(DTO)来确保类型安全和接口一致性。
- ConnectStartRequest:封装发起授权的请求参数,包含插件ID和返回URL
- ConnectCallbackPayload:封装授权回调的上下文信息,包含查询参数和当前用户资料
classDiagram
class ConnectStartRequest {
-String pluginId
-String returnUrl
+__construct(pluginId, returnUrl)
+getPluginId() String
+getReturnUrl() String
}
class ConnectCallbackPayload {
-String pluginId
-Array query
-Array userProfile
+__construct(pluginId, query, userProfile)
+getPluginId() String
+getQuery() Array
+getUserProfile() Array
}
图表来源
- ConnectStartRequest.php:24-58
- ConnectCallbackPayload.php:24-71
章节来源
- ConnectStartRequest.php:24-58
- ConnectCallbackPayload.php:24-71
插件清单与分组
- 每个插件提供 manifest.php,声明插件分组与 Provider 类名,便于框架扫描与装配。
- 分组用于聚合同类插件(如 shipping、payment、connect),支持按组筛选与默认选择。
flowchart TD
Start(["读取 manifest.php"]) --> ReadMeta["读取 plugin_group / provider"]
ReadMeta --> Register["注册 Provider 到容器/管理器"]
Register --> Grouped{"按分组聚合"}
Grouped --> List["生成可用插件列表"]
List --> End(["可供业务选择"])
图表来源
- manifest.php(快递):7-10
章节来源
- manifest.php(快递):7-10
异步任务驱动契约
- 为异步供应商(图像/视频等)提供统一接口:提交任务与轮询状态。
- 同步供应商不实现该接口,调度器据此拒绝异步提交,保障类型安全。
sequenceDiagram
participant Caller as "调用方"
participant Driver as "AsyncDriverInterface"
participant Vendor as "供应商API"
Caller->>Driver : submitTask(config, params)
Driver->>Vendor : 提交任务
Vendor-->>Driver : {provider_task_id, status, raw}
Driver-->>Caller : 返回任务ID与初始状态
loop 轮询直到完成
Caller->>Driver : pollTask(config, provider_task_id)
Driver->>Vendor : 查询状态
Vendor-->>Driver : {status, result?, expires_at?, error?}
Driver-->>Caller : 返回当前状态
end
图表来源
- AsyncDriverInterface.php:21-51
章节来源
- AsyncDriverInterface.php:21-51
请求分发与插件调用点
- Dispatcher 负责在中间件管道内懒实例化控制器并调用方法,插件能力可在控制器或服务中被调用。
- 未匹配路由直接返回 null,交由各端 Router 渲染专属 404,保持解耦。
sequenceDiagram
participant Client as "客户端"
participant Router as "端路由器"
participant Disp as "Dispatcher"
participant MW as "中间件管道"
participant Ctrl as "控制器"
Client->>Router : 请求
Router->>Disp : 分发计划
Disp->>MW : 执行中间件
MW->>Ctrl : 懒实例化并调用方法
Ctrl-->>MW : 返回结果
MW-->>Client : 响应
图表来源
- Dispatcher.php:25-45
章节来源
- Dispatcher.php:25-45
依赖关系分析
- 松耦合:业务仅依赖抽象(接口/契约),通过容器注入具体实现。
- 可替换性:同一分组下可存在多个 Provider,按策略选择;当某 Provider 不可用时,可通过空实现降级。
- 可扩展性:新增插件只需实现对应契约并提供 manifest.php,即可被框架识别与装配。
- 新增 第三方登录插件通过统一的 ConnectPluginProviderInterface 契约,实现了登录方式的无缝替换。
graph LR
Biz["业务模块"] --> IFace["插件契约/接口"]
IFace --> ImplA["真实实现"]
IFace --> ImplB["空实现"]
ImplA --> ProvA["插件提供者A"]
ImplB --> ProvB["插件提供者B"]
ConnIFace["第三方登录契约"] --> ConnImplA["AmazonProvider"]
ConnIFace --> ConnImplB["WxloginProvider"]
图表来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
- ConnectPluginProviderInterface.php:27-58
章节来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
- ConnectPluginProviderInterface.php:27-58
性能考量
- 懒加载:控制器与方法在中间件管道中懒实例化,减少不必要的对象创建。
- 空实现兜底:无插件表或模块缺失时,空实现避免数据库访问与反射开销。
- 分组与缓存:建议对分组与 Provider 列表做缓存,降低重复扫描成本。
- 异步任务:长耗时操作通过异步驱动接口提交与轮询,避免阻塞主流程。
- 新增 第三方登录插件通过Service层缓存配置和用户会话,减少重复的HTTP请求。
故障排查指南
- 现象:调用插件能力时报类不存在或方法无效
- 检查插件模块是否仍在磁盘,容器工厂会据此切换至空实现
- 确认 manifest.php 中的 provider 类名是否正确
- 现象:分组查询为空或默认值不符合预期
- 核对插件分组字段与业务期望是否一致
- 在无插件表场景下,空实现返回固定默认值(例如默认支付方式)
- 现象:异步任务无法提交
- 确认供应商驱动是否实现了异步接口;未实现的将被拒绝提交
- 新增 现象:第三方登录功能异常
- 检查 ConnectPluginProviderInterface 接口的实现是否完整
- 验证 ConnectStartRequest 和 ConnectCallbackPayload 的数据传递是否正确
- 确认 OAuth 授权流程和回调地址配置是否正确
章节来源
- PluginServiceProvider.php:32-59
- NullPluginService.php:24-129
- AsyncDriverInterface.php:21-51
- ConnectPluginProviderInterface.php:27-58
结论
DouPHP 的插件架构以"契约+容器+Provider"为核心,实现了高内聚、低耦合的可插拔体系。通过空实现兜底、分组管理与异步契约,既保证了系统的稳定性与可维护性,也为后续扩展(如更多支付、物流、AI 能力)提供了清晰的路径。新增的第三方登录统一架构进一步简化了登录方式的集成,通过标准化的接口和DTO定义,使得新登录方式的接入更加便捷和一致。建议在开发新插件时严格遵循契约与清单规范,充分利用分组与默认策略,以获得最佳的可扩展性与可观测性。
附录
- 术语
- 插件:以 Provider 形式暴露能力的可插拔单元
- 分组:同类插件的逻辑集合,便于筛选与默认选择
- 契约:插件对外暴露的方法与数据结构约定
- 空实现:在插件不可用时的降级实现,保证调用面稳定
- 新增 DTO:数据传输对象,用于规范插件间的参数传递
- 新增 第三方登录契约:ConnectPluginProviderInterface,统一登录插件的标准接口
- 最佳实践
- 明确插件分组与元数据,便于管理与展示
- 使用 Service 层封装复杂逻辑,Provider 仅做适配
- 对长耗时操作采用异步驱动接口
- 在业务侧优先通过容器注入契约,避免硬编码实现类
- 新增 第三方登录插件应遵循统一的Provider/Service架构模式
- 新增 使用DTO对象确保类型安全和接口一致性