文档目录
门面模式系统

简介

本技术文档围绕 DouPHP 的门面模式系统展开,重点解释以下主题:

  • 静态方法调用的拦截与动态代理机制(基于 __callStatic)
  • 根命名空间短名到完整类名的惰性映射(基于 SPL 自动加载 + class_alias)
  • 门面类的注册过程与容器绑定关系
  • 已注册的核心门面清单(DB、Session、Storage、Request、Route 等)
  • 门面模式的优点(简化 API、统一访问入口)
  • 自定义门面的创建步骤与实践建议
  • 性能考量(惰性加载、缓存、单例)
  • 错误处理与调试技巧

项目结构

DouPHP 的门面体系由“基础能力”和“具体门面”两部分组成:

  • 基础能力位于 core/foundation/facade:
    • AliasLoader:负责将根命名空间短名(如 DB、Session)通过 SPL 自动加载器在首次使用时映射到真实类。
    • StaticFacade:提供静态门面基类,实现 __callStatic 拦截并转发到底层实例。
  • 具体门面位于 core/facade 与 core/filesystem:
    • DB、Session、Request、Route 等门面分别指向底层服务(如 Connection、Session、Request、DelegatingRouter)。
    • Storage 门面指向 FilesystemManager,用于统一的存储抽象。
graph TB
subgraph "基础能力"
A["AliasLoader<br/>惰性别名加载"]
B["StaticFacade<br/>静态代理基类"]
end
subgraph "具体门面"
C["DB"]
D["Session"]
E["Request"]
F["Route"]
G["Storage"]
end
A --> |class_alias 暴露根命名空间短名| C
A --> |class_alias 暴露根命名空间短名| D
A --> |class_alias 暴露根命名空间短名| E
A --> |class_alias 暴露根命名空间短名| F
A --> |class_alias 暴露根命名空间短名| G
C --> |"__callStatic 转发"| B
D --> |"__callStatic 转发"| B
E --> |"__callStatic 转发"| B
F --> |"__callStatic 转发"| B
G --> |"__callStatic 转发"| B

核心组件

  • 惰性别名加载器(AliasLoader)
    • 职责:维护「短名 => 完整类名」映射;在首次解析时通过 SPL 自动加载器调用 load(),使用 class_alias 将目标类以根命名空间短名暴露出来。
    • 关键点:单例、可追加合并额外别名、幂等注册、未命中时静默返回 false 让 PHP 继续标准行为。
  • 静态门面基类(StaticFacade)
    • 职责:实现 __callStatic,将静态方法调用转发到底层实例;提供 swap/clearResolvedInstance/clearAllResolvedInstances 测试期替换能力。
    • 关键点:通过 getAccessor() 获取容器键;从容器解析底层实例;若未绑定则抛出运行时异常。
  • 核心门面
    • DB:底层为数据库连接(Connection),提供链式查询与事务等方法。
    • Session:底层为会话管理(Session),提供读写、闪存等接口。
    • Request:底层为 HTTP 请求封装(Request),提供输入、头、路径等读取方法。
    • Route:入站路由门面,底层为 DelegatingRouter,负责分发与当前路由状态查询。
    • Storage:存储门面,底层为 FilesystemManager,提供磁盘选择与构建能力。

架构总览

下图展示了从业务代码调用到最终执行的具体流程:业务侧以根命名空间短名调用门面静态方法,SPL 自动加载器触发别名映射,随后通过 __callStatic 将调用转发至容器中的底层实例。

sequenceDiagram
participant App as "业务代码"
participant Loader as "AliasLoader"
participant Facade as "具体门面(如 DB)"
participant Base as "StaticFacade"
participant Container as "容器(Container)"
participant Service as "底层实例(Connection/Session/...)"
App->>Facade : "DB : : table(...)"
Note over App,Loader : "首次遇到根命名空间短名"
Loader-->>App : "class_alias('DB', 'Dou\\Core\\Facade\\DB')"
App->>Facade : "DB : : table(...)"
Facade->>Base : "__callStatic('table', args)"
Base->>Container : "make(getAccessor())"
Container-->>Base : "Service 实例"
Base-->>Facade : "调用 service.table(...)"
Facade-->>App : "返回结果"

详细组件分析

惰性别名加载器(AliasLoader)

  • 工作原理
    • 通过 spl_autoload_register 注册回调 load(),仅在首次解析未知类时触发。
    • 如果当前类名存在于别名映射表,则使用 class_alias 将目标类以根命名空间短名暴露。
    • 未命中时返回 false,交由 PHP 继续标准 Class not found 流程,避免干扰其他自动加载器。
  • 生命周期与扩展点
    • getInstance() 支持传入额外别名进行追加合并,便于插件或模块在启动后期补充自己的短名。
    • addAlias() 可在运行期动态添加新别名,无需重新注册 SPL 回调。
    • getAliases() 可用于测试与调试,查看当前已登记的映射。
  • 性能特征
    • 惰性:仅在首次解析时执行映射。
    • 单次 class_alias:同一短名仅映射一次。
    • 低开销:未命中时快速失败,不抛错。
flowchart TD
Start(["进入 load(alias)"]) --> Check{"alias 是否在映射表中?"}
Check -- 是 --> DoAlias["class_alias(target, alias)"]
DoAlias --> ReturnTrue["返回 true"]
Check -- 否 --> ReturnFalse["返回 false"]
ReturnTrue --> End(["结束"])
ReturnFalse --> End

静态门面基类(StaticFacade)

  • 工作原理
    • 子类必须实现 getAccessor(),返回容器中以该底层实例为 key 的字符串。
    • getFacadeRoot() 优先检查 swap 槽(测试注入),否则从容器 make 出实例。
    • __callStatic($method, $args) 将静态方法调用转发给底层实例的同名方法。
  • 错误处理
    • 若容器未绑定对应 accessor,抛出运行时异常,提示确保在静态调用前完成注册。
  • 测试支持
    • swap():临时替换底层实例。
    • clearResolvedInstance():清理当前门面的 swap 槽。
    • clearAllResolvedInstances():清理全部 swap 槽。
classDiagram
class StaticFacade {
-static resolvedInstances
+getFacadeRoot() object
+swap(instance) void
+clearResolvedInstance() void
+clearAllResolvedInstances() void
+__callStatic(method, args) mixed
#getAccessor() string
}
class DB {
+getAccessor() string
}
class Session {
+getAccessor() string
}
class Request {
+getAccessor() string
}
class Route {
+getAccessor() string
}
class Storage {
+getAccessor() string
}
StaticFacade <|-- DB
StaticFacade <|-- Session
StaticFacade <|-- Request
StaticFacade <|-- Route
StaticFacade <|-- Storage

核心门面详解

DB 门面

  • 作用:对数据库连接的静态访问入口,提供链式查询、聚合、事务等能力。
  • 底层绑定:容器中以 Connection 类名为 key。
  • 典型用法:通过 DB::table(...) 构建查询,或使用 beginTransaction/commit/rollback 控制事务。

Session 门面

  • 作用:对会话管理的静态访问入口,提供读写、数组存取、闪存等功能。
  • 底层绑定:容器中以 \Dou\Core\Infra\Session\Session 类名为 key。
  • 典型用法:set/get/del/has/push/pull/inc/decrement/setFlash/getFlash/pullAllFlashes 等。

Request 门面

  • 作用:对 HTTP 请求的静态访问入口,提供输入、查询参数、头部、路径、URL、验证等能力。
  • 底层绑定:容器中以 \Dou\Core\Web\Http\Request 类名为 key。
  • 典型用法:input/query/post/cookie/server/only/except/validate 等。

Route 门面

  • 作用:入站路由门面,负责设置委托路由器、触发分发、查询当前路由状态。
  • 底层绑定:容器中以 DelegatingRouter 类名为 key。
  • 典型用法:setDelegate(dispatch/current)。

Storage 门面

  • 作用:存储门面,提供磁盘选择、构建、扩展、默认驱动与上传默认配置等能力。
  • 底层绑定:容器中以 FilesystemManager 类名为 key。
  • 典型用法:disk/build/extend/getDiskConfig/getDefaultDriverName/getDiskNames/getUploadDefaults。

门面注册与短名映射

  • 短名到完整类名的映射由 AliasLoader 维护,并通过 SPL 自动加载器在首次解析时生效。
  • 可通过 getInstance([...]) 一次性注入多组映射,或通过 addAlias() 在运行期追加。
  • 一旦 class_alias 生效,业务代码即可直接使用根命名空间短名(如 DB、Session)调用静态方法。
sequenceDiagram
participant Boot as "启动阶段"
participant Loader as "AliasLoader"
participant SPL as "SPL 自动加载队列"
participant App as "业务代码"
Boot->>Loader : "getInstance(初始映射)"
Boot->>Loader : "register()"
Loader->>SPL : "spl_autoload_register(load)"
App->>App : "首次使用 DB"
SPL-->>Loader : "load('DB')"
Loader-->>App : "class_alias('Dou\\Core\\Facade\\DB', 'DB')"
App->>DB : "DB : : table(...)"

依赖关系分析

  • 耦合与内聚
    • 门面与底层服务通过容器解耦:门面只关心 getAccessor() 返回的键,不直接依赖具体实现细节。
    • AliasLoader 与业务代码弱耦合:仅在首次解析时介入,不影响后续调用路径。
  • 外部依赖
    • 容器(Container):负责实例化与缓存底层服务。
    • SPL 自动加载器:负责触发别名映射。
  • 潜在循环依赖
    • 门面本身无状态,仅转发调用,不易形成循环依赖;需关注容器绑定顺序,确保在首次静态调用前完成注册。
graph LR
Facade["门面(DB/Session/Request/Route/Storage)"] --> Base["StaticFacade"]
Base --> Container["容器(Container)"]
Container --> Service["底层实例(Connection/Session/Request/Router/Manager)"]
Loader["AliasLoader"] --> Facade

性能考虑

  • 惰性加载
    • 别名映射仅在首次解析时执行,避免启动期开销。
    • 门面调用仅在需要时解析底层实例,减少不必要的初始化。
  • 缓存机制
    • 容器通常会对已解析的实例进行缓存(单例/共享实例),降低重复创建成本。
    • class_alias 的结果在进程生命周期内有效,无需重复映射。
  • 优化建议
    • 合理组织门面注册顺序,确保关键门面尽早可用。
    • 避免在高频路径中频繁 swap/clearResolvedInstance,影响稳定性与性能。
    • 对于重资源底层服务,尽量延迟到实际使用时再解析。

故障排查指南

  • 常见问题
    • 未绑定 accessor:调用门面静态方法时报错,提示容器未注册对应 accessor。
    • 别名未生效:根命名空间短名无法解析,检查是否已 register 且映射正确。
  • 定位步骤
    • 确认 AliasLoader 已注册并包含所需短名映射。
    • 确认容器已在门面首次调用前完成底层实例绑定。
    • 使用 getAliases() 检查当前已登记的映射。
    • 使用 swap() 注入 mock 以隔离外部依赖,逐步缩小问题范围。
  • 调试技巧
    • 在门面调用前后记录日志,观察 __callStatic 是否被触发。
    • 临时清空 swap 槽(clearResolvedInstance/clearAllResolvedInstances)恢复容器解析路径。
    • 针对特定门面(如 DB/Session/Request/Route/Storage)单独验证其 getAccessor 返回值是否正确。

结论

DouPHP 的门面模式通过“惰性别名加载 + 静态代理”的组合,提供了简洁一致的 API 访问方式:

  • 业务代码以短名调用门面,无需关心底层实现与命名空间。
  • 通过容器解耦,便于测试与替换实现。
  • 惰性加载与缓存机制保障性能。
  • 完善的错误处理与调试手段帮助快速定位问题。

附录

已注册的核心门面清单

  • DB:数据库连接门面(底层 Connection)
  • Session:会话管理门面(底层 Session)
  • Storage:存储门面(底层 FilesystemManager)
  • Request:HTTP 请求门面(底层 Request)
  • Route:入站路由门面(底层 DelegatingRouter)

如何创建自定义门面(示例步骤)

  • 步骤
    1. 定义一个继承自 StaticFacade 的门面类,实现 getAccessor() 返回容器键。
    2. 在容器启动阶段将该门面所需的底层实例以指定键绑定。
    3. 如需根命名空间短名调用,向 AliasLoader 注册短名到门面完整类名的映射。
    4. 在业务代码中通过短名调用门面静态方法。
  • 参考路径
    • 门面基类与代理逻辑:StaticFacade.php
    • 别名加载与注册:AliasLoader.php
    • 现有门面示例:DB、Session、Request、Route、Storage
添加日期:2026-10-05