简介
本技术文档面向 DouPHP 的路由管理系统,围绕以下目标展开:
- 深入解释 RouteCollector 的路由收集机制(自动发现、手动注册、动态加载)
- 详细说明 RouteManifest 的路由清单管理(元数据、依赖关系、版本控制思路)
- 记录 RouteTableExporter 的路由表导出能力(性能分析、调试输出、部署优化)
- 解释 ModuleRegistry 的模块路由注册机制(模块化组织与管理)
- 深入分析 BackendDeclaredMatcher 的后端声明匹配器(匹配算法与性能优化)
- 提供最佳实践(命名规范、组织结构、维护策略)与调试排错方法
- 面向初学者讲解基础概念,为高级开发者提供大规模应用的路由架构设计指南
项目结构
DouPHP 的路由系统位于 core/web/routing 下,采用“声明式 + 配置式”混合模式:
- 声明式:三端(front/admin/api)route/*.php 通过 fluent API 或返回数组注册路由
- 配置式:config/route.php 定义 page/column/simple 风格规则
- 构建期:RouteManifestBuilder 统一扫描并生成 RouteEntry[] 清单
- 运行期:各端 Matcher 基于清单进行 URL 匹配与分发
graph TB
A["前端/后台/API 入口"] --> B["RouteManifestBuilder<br/>扫描三端 route 文件夹"]
B --> C["RouteManifest<br/>进程级缓存条目"]
C --> D["BackendDeclaredMatcher / PrettyRouteMatcher<br/>URL 匹配"]
C --> E["RouteTableExporter<br/>导出命名路由表"]
B --> F["RouteCollector<br/>fluent API 累积器"]
B --> G["RouteRules<br/>读取 config/route.php"]
G --> C
核心组件
- RouteCollector:在 include 路由文件期间累积 fluent API 生成的 RouteEntry,支持组属性栈与 source 追踪
- RouteManifestBuilder:聚合 home、系统内置 declared、风格规则、三端 declared 条目,产出完整清单
- RouteManifest:不可变可枚举的数据源,提供按类型过滤、具名反查、分组视图与进程级缓存
- RouteTableExporter:按端导出 name→pattern 映射,供小程序等外部消费
- ModuleRegistry:模块分类单一来源,提供固定前台模块、保留首段、栏目/单表判定等
- BackendDeclaredMatcher:后端(Admin/Api)declared 条目的匹配器,实现 specificity 排序与 405 处理
- RouteResourceBuilder / RouteEntryBuilder:fluent API 的具体实现,负责资源与动作的展开与入队
架构总览
整体流程分为“构建期”和“运行期”两个阶段:
- 构建期:RouteManifestBuilder 扫描三端 route 目录,执行每个路由文件的 include,将 fluent API 累积的条目与 return 数组合并;同时读取 config/route.php 的风格规则,生成 meta 模板条目;最终产出 RouteEntry[] 清单并缓存
- 运行期:Matcher 从清单中筛选 declared 条目,编译为正则进行匹配;若命中则返回控制器与方法;未命中时根据 HTTP 方法返回 405;空路径回落到 index
sequenceDiagram
participant R as "请求"
participant M as "BackendDeclaredMatcher"
participant L as "RouteManifest"
participant P as "PrettyUrlCompiler"
participant C as "控制器"
R->>M : match(routeRaw, end, httpMethod)
M->>L : getEntriesByType('declared')
loop 遍历候选
M->>P : compileToRegex(pattern, params)
P-->>M : regex
M->>M : 收集命中候选
end
M->>M : specificity 排序
alt 有命中且方法允许
M-->>R : {status : 'hit', fqcn, action, params}
R->>C : 调用控制器方法
else 方法不允许
M-->>R : {status : 'method_not_allowed', allow : [...]}
else 无命中
M-->>R : null (404)
end
详细组件分析
RouteCollector:路由收集机制
- 作用:在每个路由文件 include 期间安装 collector,fluent API 产生的 RouteEntry 被有序推入;include 结束后 flush 取出并入 manifest
- 关键能力:
- 组属性栈:pushGroupAttributes/popGroupAttributes 维护 name 前缀与 compositeModule 标志
- source 追踪:sourceFor(name) 生成 'declared:<rel>:<name>' 用于定位来源
- 相对路径推导:deriveRelPath 计算相对于 ROOT_PATH 的正斜杠路径,便于诊断
- 自动发现与动态加载:由 RouteManifestBuilder 扫描三端 route 目录并 include 文件,collector 在 include 期间工作
- 手动注册:路由文件中通过 Route::get/post/resource/group 等 fluent API 注册
classDiagram
class RouteCollector {
-string file
-string relPath
-RouteEntry[] entries
-array groupStack
+__construct(file)
+pushGroupAttributes(attrs)
+popGroupAttributes()
+currentNamePrefix() string
+currentComposite() bool
+relPath() string
+sourceFor(name) string
+push(entry)
+flush() RouteEntry[]
}
RouteManifest:路由清单管理
- 作用:承载 home/static/system_reserved/family/page/column/column_short/simple/declared 的物化清单,提供进程级缓存
- 关键能力:
- getEntries/getEntriesByType:按顺序或类型获取条目
- getRuleGroups:供 UrlBuilder 出站生成使用(不含家族/具名条目)
- getEntryByName:具名反查(如 user_center.member)
- clearCache/isBuilt:测试与热更场景下的缓存控制
- 元数据与依赖:
- 条目字段契约见 RouteEntry 类注释;构建期依赖 ModuleRegistry 与 RouteRules
- 版本控制建议:通过 source 字段记录来源文件与名称,配合 devtools 工具在启动期断言唯一性
classDiagram
class RouteManifest {
-static array $entries
-static array $nameIndex
-static array $ruleGroups
+clearCache()
+getEntries() RouteEntry[]
+getEntriesByType(type) RouteEntry[]
+getRuleGroups() array
+getEntryByName(name) RouteEntry?
+isBuilt() bool
}
RouteTableExporter:路由表导出
- 作用:按端导出 name→pattern 映射,供小程序等外部消费;仅取 declared 条目,剥去 api. 前缀
- 性能与调试:
- 只遍历 declared 条目,避免无关 meta 模板开销
- 可按端过滤,减少不必要的前缀处理
- 部署优化:
- 可在构建期生成静态路由表文件,减少运行时查找成本
- 结合缓存策略(如文件缓存)提升小程序侧解析速度
flowchart TD
Start(["开始"]) --> Filter["遍历 RouteManifest::getEntries()"]
Filter --> CheckType{"route_type == 'declared'?"}
CheckType --> |否| Filter
CheckType --> |是| CheckEnd{"endNamespace == 指定端?"}
CheckEnd --> |否| Filter
CheckEnd --> |是| CheckName{"name 非空?"}
CheckName --> |否| Filter
CheckName --> |是| Normalize["去除 api. 前缀"]
Normalize --> Table["写入 name => pattern 表"]
Table --> Sort["按键升序排序"]
Sort --> End(["结束"])
ModuleRegistry:模块路由注册机制
- 作用:模块分类单一来源,提供固定前台模块、系统保留首段、栏目/单表判定、父模块启用检查
- 与路由的关系:
- 风格规则条目(page/column/simple)在匹配时需经 ModuleRegistry 准入校验
- 短地址模块(column_short)与 column 族互斥选用
- 配置驱动:所有判定来自 Config 与命名规范(Naming),保证三端一致
classDiagram
class ModuleRegistry {
+fixedFrontModules() array
+systemReservedFirstSegments() array
+isColumn(module) bool
+isSingle(module) bool
+isFixedFront(module) bool
+isSystemReserved(segment) bool
+isParentModuleEnabled(parent) bool
}
BackendDeclaredMatcher:后端声明匹配器
- 作用:对 Admin/Api 端的 declared 条目逐条编译为 PCRE,进行 URL 匹配与 HTTP 方法过滤
- 匹配算法:
- 收集全部命中条目(不在首条即返回)
- specificity 排序:占位符少者优先 → 字面段多者优先 → 声明顺序兜底
- 若传入 HTTP 方法:首个 method 命中即 HIT;全不命中返回 405(含 allow 列表)
- 未传 HTTP 方法(permissive):返回排序后首条
- 空路径回落到 index
- 性能优化:
- 仅在需要时编译正则
- 通过 placeholderCount/literalSegmentCount 快速评估具体性
- 使用 usort 稳定排序,避免重复计算
flowchart TD
S(["match(routeRaw, end, httpMethod)"]) --> T["trim 路径"]
T --> M1["matchRouteRaw(routeRaw, end, httpMethod)"]
M1 --> C1["遍历 declared 条目并按端过滤"]
C1 --> C2["编译正则并匹配"]
C2 --> C3{"命中?"}
C3 --> |否| C1
C3 --> |是| C4["收集候选(含 placeholders/literals/order)"]
C4 --> U["usort 按 specificity 排序"]
U --> H{"httpMethod 为空?"}
H --> |是| R1["返回首条 hit"]
H --> |否| V["遍历候选找允许的方法"]
V --> A{"找到允许方法?"}
A --> |是| R2["返回 hit"]
A --> |否| R3["返回 405 with allow"]
RouteResourceBuilder:资源路由展开
- 作用:由 Route::resource() 创建,按 RESTful 标准动作集展开为多条 RouteEntry
- 默认动作集:index/create/store/edit/update/destroy(show 需 only() 显式开启)
- 扩展能力:prefix/sub/only/except/动词方法 extras,最终 push 到 collector
- 命名规则:index → nameBase;其余 → nameBase.action;nameBase 受 sub 影响
classDiagram
class RouteResourceBuilder {
-RouteCollector collector
-string module
-string controller
-string prefix
-string sub
-string keyName
-string keyPattern
-array onlyList
-array exceptList
-array extraBuckets
-bool registered
+prefix(prefix) self
+sub(sub) self
+only(list) self
+except(list) self
+get(actions) self
+post(actions) self
+put(actions) self
+patch(actions) self
+delete(actions) self
+any(actions) self
}
示例:后台路由注册
- admin/route/index.php:演示 name('admin.') 与 group/resource 的使用
- admin/route/ai.php:演示 resource 与 group 的组合,prefix/sub/only/动词 extras 的链式调用
依赖关系分析
- RouteManifestBuilder 依赖:
- ModuleRegistry:模块分类与启用判定
- RouteRules:读取 config/route.php 风格规则
- RouteCollector:收集 fluent API 产物
- RouteEntry:条目值对象
- BackendDeclaredMatcher 依赖:
- RouteManifest:获取 declared 条目
- PrettyUrlCompiler:编译 pattern 为正则
- RouteTableExporter 依赖:
- RouteManifest:遍历条目并过滤
- 配置文件:
- config/route.php:定义 page/column/simple 风格规则,影响匹配顺序与模块准入
graph LR
RB["RouteManifestBuilder"] --> MR["ModuleRegistry"]
RB --> RR["RouteRules"]
RB --> RC["RouteCollector"]
RB --> RE["RouteEntry"]
BM["BackendDeclaredMatcher"] --> RM["RouteManifest"]
BM --> PC["PrettyUrlCompiler"]
TE["RouteTableExporter"] --> RM
CFG["config/route.php"] --> RR
性能考量
- 构建期优化:
- 进程级缓存:RouteManifest 首次构建后缓存 entries/nameIndex/ruleGroups,避免重复扫描
- 按需构建:getRuleGroups 懒加载,仅当 UrlBuilder 需要时构建
- 运行期优化:
- 匹配器仅遍历 declared 条目,减少无关 meta 模板开销
- specificity 排序确保高优先级规则优先命中,降低平均匹配成本
- 正则编译仅在匹配时进行,避免预编译全部 pattern
- 导出优化:
- RouteTableExporter 仅处理 declared 条目,并按端过滤,减少不必要的前缀处理
- 可结合文件缓存或构建期生成静态表,进一步降低运行时开销
故障排除指南
- 常见问题:
- 404 未命中:检查 routeRaw 是否为空(空路径会回落到 index);确认 declared 条目是否包含该端且 controller 非空
- 405 方法不允许:确认 entry->acceptsMethod(httpMethod);查看 allow 列表以了解支持的方法
- 具名路由失败:确认 name 唯一性(devtools 启动期断言);检查 getEntryByName 是否能查到对应条目
- 调试建议:
- 使用 permissive 调用(httpMethod=null)查看排序后首条候选,辅助定位 specificity 问题
- 检查 source 字段定位路由来源文件与名称
- 利用 RouteManifest::clearCache() 在配置热更后重置缓存
- 工具与脚本:
- devtools/url-route-scan.php:可用于扫描路由使用情况
- devtools/route-list.php:启动期断言 name 唯一性
结论
DouPHP 的路由系统通过“声明式 + 配置式”的混合模式,实现了灵活、可扩展且高性能的路由管理:
- RouteCollector 提供 fluent API 的副作用累积,便于模块化注册
- RouteManifestBuilder 统一聚合多源条目,保证匹配顺序与一致性
- RouteManifest 作为不可变数据源,提供高效查询与缓存
- RouteTableExporter 支持外部消费,便于前后端协同
- ModuleRegistry 提供统一的模块分类与准入判定
- BackendDeclaredMatcher 实现精确的 specificity 排序与 405 处理
最佳实践包括:
- 命名规范:使用点分命名(module.action),保持唯一性与可读性
- 组织结构:按端划分 route 目录,资源路由优先使用 resource,复杂场景使用 group
- 维护策略:通过 source 字段追踪来源,结合 devtools 工具进行诊断与验证
附录
- 风格规则参考:config/route.php 定义了 page/column/simple 的多套风格,便于不同站点形态的快速适配
- 示例路由:admin/route/index.php 与 admin/route/ai.php 展示了常见用法