简介
本技术文档聚焦 DouPHP 的路由匹配与美化 URL(Pretty URL)体系,系统性阐述:
- 路由优先级排序机制:静态优先、正则匹配、通配符处理与消歧策略。
- PrettyRouteMatcher 的美化路由匹配算法与 SEO 友好 URL 的解析规则及反向生成逻辑。
- 路由缓存策略:编译时缓存、运行时缓存与缓存失效机制。
- 性能优化技巧:路由预编译、索引优化与内存管理。
- 调试工具与性能分析方法:如何定位命中顺序、方法冲突与路径歧义。
项目结构
DouPHP 将“入站匹配”和“出站构建”解耦为独立子系统,并通过统一的数据源 RouteManifest 保证双向一致性:
- 入站匹配器:前台 PrettyRouteMatcher、后端 BackendDeclaredMatcher。
- 模式编译器:PrettyUrlCompiler(双向可逆)。
- 数据清单:RouteManifest / RouteEntry(不可变、可枚举)。
- 出站构建器:UrlBuilder(与入站共用同一 pattern 引擎)。
- 规则选择:RouteRules(按站点风格选择 page/column/simple 等模板族)。
graph TB
A["请求入口"] --> B["PrettyRouteMatcher<br/>前台匹配"]
A --> C["BackendDeclaredMatcher<br/>Admin/Api 匹配"]
B --> D["RouteManifest<br/>声明式条目"]
C --> D
D --> E["RouteEntry<br/>条目值对象"]
B --> F["PrettyUrlCompiler<br/>正则编译/填充"]
C --> F
G["UrlBuilder<br/>出站URL构建"] --> F
G --> D
H["RouteRules<br/>风格选择"] --> G
图示来源
- PrettyRouteMatcher.php:25-43
- BackendDeclaredMatcher.php:21-37
- RouteManifest.php:21-37
- PrettyUrlCompiler.php:21-35
- UrlBuilder.php:26-62
- RouteRules.php:23-32
章节来源
- PrettyRouteMatcher.php:25-43
- BackendDeclaredMatcher.php:21-37
- RouteManifest.php:21-37
- PrettyUrlCompiler.php:21-35
- UrlBuilder.php:26-62
- RouteRules.php:23-32
核心组件
- PrettyRouteMatcher:前台外观 URL 匹配器,基于 RouteManifest 中 Front 命名空间的 declared 条目进行匹配;支持短地址策略与空串首页短路。
- BackendDeclaredMatcher:Admin/Api 端声明式匹配器,同构于前台匹配器,但按端隔离并返回 method_not_allowed 语义。
- PrettyUrlCompiler:pattern 迷你语言编译器,提供 compileToRegex(入站)与 fill(出站),进程级缓存正则。
- RouteManifest:不可变路由清单,提供 getEntries/getEntriesByType/getRuleGroups/getEntryByName,进程级缓存。
- RouteEntry:条目值对象,承载 pattern、params、controller、methods、中间件元数据等,并提供 endNamespace、acceptsMethod。
- UrlBuilder:站点 URL 统一构建器,按 kind 分发生成伪静态路径,复用 PrettyUrlCompiler 保证双向一致。
- RouteRules:合并配置并按 site.route_* 选择风格,供 UrlBuilder 与匹配器共享。
章节来源
- PrettyRouteMatcher.php:25-43
- BackendDeclaredMatcher.php:21-37
- PrettyUrlCompiler.php:21-35
- RouteManifest.php:21-37
- RouteEntry.php:21-34
- UrlBuilder.php:26-62
- RouteRules.php:23-32
架构总览
入站匹配流程(前台):
- 接收已去语言前缀的 route 字符串。
- 空串直接命中 IndexController。
- 若启用短地址且传入带模块前缀的长格式,直接拒绝(避免歧义)。
- 遍历预编译的前台 declared 规则,收集全部命中候选。
- 按 specificity 排序(占位符少 → 字面段多 → 声明顺序),再按 HTTP 方法过滤。
- 未命中则尝试短地址兜底:补回模块前缀再用同一套规则重试。
入站匹配流程(后台 Admin/Api):
- 仅扫描对应端的 declared 条目(按 controller 命名空间判定端归属)。
- 同样收集全部命中候选,按 specificity 排序与方法过滤。
- 无命中返回 null;方法不匹配返回 method_not_allowed 允许列表。
出站 URL 构建:
- 根据 intent['kind'] 选择 list/detail/category/class/action2/action3 分支。
- 通过 RouteManifest 提供的 page/column/simple 模板族,结合 ShortUrlPolicy 决定最终路径片段。
- 使用 PrettyUrlCompiler::fill 填充 pattern,追加 ROOT_URL、语言前缀与分页。
sequenceDiagram
participant Client as "客户端"
participant Matcher as "PrettyRouteMatcher"
participant Manifest as "RouteManifest"
participant Compiler as "PrettyUrlCompiler"
participant Builder as "UrlBuilder"
Client->>Matcher : normalize(route, httpMethod)
alt 空串首页
Matcher-->>Client : 命中 IndexController
else 非空
Matcher->>Manifest : getEntriesByType('declared')
loop 遍历规则
Matcher->>Compiler : compileToRegex(pattern, params)
Compiler-->>Matcher : PCRE 正则
Matcher->>Matcher : 收集命中候选 + 计算 specificity
end
Matcher->>Matcher : usort 排序 + 方法过滤
alt 未命中且启用短地址
Matcher->>Matcher : 补回模块前缀重试
end
Matcher-->>Client : {matched, module, target, controller, action, sub, params}
end
Note over Builder,Client : 出站 URL 构建由 UrlBuilder 完成,复用同一 compiler
图示来源
- PrettyRouteMatcher.php:61-114
- PrettyRouteMatcher.php:116-169
- PrettyUrlCompiler.php:51-69
- UrlBuilder.php:127-153
章节来源
- PrettyRouteMatcher.php:61-114
- PrettyRouteMatcher.php:116-169
- BackendDeclaredMatcher.php:49-62
- BackendDeclaredMatcher.php:72-126
- PrettyUrlCompiler.php:51-69
- UrlBuilder.php:127-153
详细组件分析
路由优先级与消歧机制
- 收集全部命中:为避免“首条命中即返回”导致的次优匹配,先收集所有命中候选。
- Specificity 排序:
- 占位符数量越少越优先(更具体)。
- 字面段越多越优先(如 article/featured 优于 article/{id})。
- 声明顺序作为兜底。
- HTTP 方法过滤:
- 若传入 httpMethod,按排序逐个检查 acceptsMethod;首个命中即返回。
- 全不命中时,后台返回 method_not_allowed 并附带 allow 列表;前台视为未命中交由 Resolver 派发 page_wrong。
- 短地址兜底:
- 前台在短地址启用时,若原路径未命中任何规则,会补回模块前缀再用同一套 declared 规则重试,确保「省略前缀的短链」与「完整规则模板」可逆。
flowchart TD
Start(["开始"]) --> Collect["收集全部命中候选"]
Collect --> Empty{"有候选?"}
Empty -- 否 --> ReturnNull["返回 null未命中"]
Empty -- 是 --> Sort["按 specificity 排序<br/>占位符少→字面段多→声明顺序"]
Sort --> MethodCheck{"是否指定HTTP方法?"}
MethodCheck -- 否 --> FirstHit["返回排序后首条"]
MethodCheck -- 是 --> Iterate["按序遍历检查 acceptsMethod"]
Iterate --> Hit{"找到方法命中?"}
Hit -- 是 --> ReturnHit["返回命中结果"]
Hit -- 否 --> MethodNotAllowed["返回 method_not_allowed + allow"]
ReturnNull --> End(["结束"])
FirstHit --> End
ReturnHit --> End
MethodNotAllowed --> End
图示来源
- PrettyRouteMatcher.php:116-169
- BackendDeclaredMatcher.php:72-126
- RouteEntry.php:198-216
章节来源
- PrettyRouteMatcher.php:116-169
- BackendDeclaredMatcher.php:72-126
- RouteEntry.php:198-216
PrettyRouteMatcher 的美化路由匹配算法
- 入口 normalize:
- 空串短路到 IndexController。
- 短地址启用时禁止带模块前缀的长格式。
- 调用 matchRoutePatterns 匹配 declared 规则。
- 未命中且启用短地址时,补回模块前缀重试。
- 匹配 matchRoutePatterns:
- 遍历预编译规则,收集命中候选。
- 计算 placeholders/literals/order,usort 排序。
- 若传入 httpMethod,按 ruleAcceptsMethod 过滤。
- 结果 buildResult:
- 从 explicit* 字段提取 module/target/controller/action/sub/name。
- 具名捕获组除 module/action/sub_action 外作为 params 输出。
- 携带中间件元数据 mw_append/mw_without/mw_params/mw_skip_all。
classDiagram
class PrettyRouteMatcher {
-routePatterns : array
+normalize(route, httpMethod) array
-matchRoutePatterns(path, httpMethod) array|null
-ruleAcceptsMethod(rule, httpMethod) bool
-compareSpecificity(a, b) int
-placeholderCount(pattern) int
-literalSegmentCount(pattern) int
-buildResult(rule, captured) array
-extractAllCaptures(matches) array
-loadRoutePatterns() array
}
图示来源
- PrettyRouteMatcher.php:44-52
- PrettyRouteMatcher.php:61-114
- PrettyRouteMatcher.php:116-169
- PrettyRouteMatcher.php:171-189
- PrettyRouteMatcher.php:191-242
- PrettyRouteMatcher.php:244-310
- PrettyRouteMatcher.php:312-353
章节来源
- PrettyRouteMatcher.php:61-114
- PrettyRouteMatcher.php:116-169
- PrettyRouteMatcher.php:171-189
- PrettyRouteMatcher.php:191-242
- PrettyRouteMatcher.php:244-310
- PrettyRouteMatcher.php:312-353
SEO 友好 URL 的解析与反向生成
- 解析规则(入站):
- 统一由 PrettyUrlCompiler::compileToRegex 将 pattern 转为带具名捕获组的 PCRE。
- 可选段 [...] 在编译期转为 (?:...)?,在填充期按需渲染。
- 占位符支持内联正则 {name:regex},优先于 params[name]。
- 反向生成(出站):
- UrlBuilder 按 kind 选择模板族(page/column/simple),结合 ShortUrlPolicy 决定是否省略模块前缀或替换为分类别名。
- 使用 PrettyUrlCompiler::fill 将具名值填入 pattern,得到路径片段。
- 组合 ROOT_URL、语言前缀与分页(/oN 或 &page=N)。
sequenceDiagram
participant In as "入站匹配"
participant Out as "出站构建"
participant C as "PrettyUrlCompiler"
In->>C : compileToRegex(pattern, params)
C-->>In : PCRE 正则
In->>In : preg_match + specificity 排序
In-->>Out : 命中条目含具名捕获
Out->>C : fill(pattern, values)
C-->>Out : 路径片段
Out->>Out : 拼接 ROOT_URL/语言前缀/分页
Out-->>Client : 完整 URL
图示来源
- PrettyUrlCompiler.php:51-69
- PrettyUrlCompiler.php:71-122
- UrlBuilder.php:127-153
- UrlBuilder.php:743-787
章节来源
- PrettyUrlCompiler.php:51-69
- PrettyUrlCompiler.php:71-122
- UrlBuilder.php:127-153
- UrlBuilder.php:743-787
路由缓存策略
- 编译时缓存:
- PrettyUrlCompiler::compileToRegex 对 pattern+params 组合进行进程级缓存,键为 pattern|serialize(params)。
- 可通过 clearCache() 清空。
- 运行时缓存:
- RouteManifest 首次构建 entries/nameIndex/ruleGroups,后续直接读取;clearCache() 重置。
- UrlBuilder 维护进程级静态缓存 $urlFieldCache 与 $urlCategoryCache,用于列表预热与详情构建。
- RouteRules 缓存选中风格的规则分组。
- 缓存失效机制:
- 单测或配置热更后调用各 clearCache() 方法,确保下次访问重新构建。
flowchart TD
A["请求进入"] --> B["RouteManifest.ensureBuilt()<br/>首次构建 entries/index/groups"]
B --> C{"已构建?"}
C -- 否 --> Build["RouteManifestBuilder.build()"]
Build --> Cache["写入静态缓存"]
C -- 是 --> Use["直接使用缓存"]
Use --> D["PrettyUrlCompiler.compileToRegex()<br/>进程级正则缓存"]
D --> E["UrlBuilder.warmupUrlCache()<br/>字段/分类行缓存"]
E --> F["响应"]
G["配置热更/单测"] --> H["clearCache() 清空"]
H --> B
图示来源
- RouteManifest.php:54-59
- RouteManifest.php:137-157
- PrettyUrlCompiler.php:38-49
- UrlBuilder.php:71-75
- UrlBuilder.php:236-272
- RouteRules.php:34-42
章节来源
- RouteManifest.php:54-59
- RouteManifest.php:137-157
- PrettyUrlCompiler.php:38-49
- UrlBuilder.php:71-75
- UrlBuilder.php:236-272
- RouteRules.php:34-42
性能优化技巧
- 路由预编译:
- 前台 PrettyRouteMatcher 在构造时加载并预编译所有前台 declared 规则的 _regex,避免每次请求重复编译。
- 后台 BackendDeclaredMatcher 对每条 declared 条目即时编译,但得益于 PrettyUrlCompiler 进程级缓存,相同 pattern+params 不会重复编译。
- 索引优化:
- RouteManifest 维护 nameIndex,支持 O(1) 具名反查。
- 匹配阶段先收集全部命中,再一次性排序,减少多次比较开销。
- 内存管理:
- 使用值对象 RouteEntry 承载条目,避免散乱数组。
- 进程级静态缓存跨请求保留,减少重复 I/O 与计算。
- UrlBuilder 列表预热 warmupUrlCache 批量拉取 slug/time/category_id,降低详情页逐条查库。
章节来源
- PrettyRouteMatcher.php:46-52
- PrettyRouteMatcher.php:312-353
- BackendDeclaredMatcher.php:72-126
- PrettyUrlCompiler.php:38-49
- RouteManifest.php:107-120
- UrlBuilder.php:236-272
依赖关系分析
- PrettyRouteMatcher 依赖:
- RouteManifest(获取前台 declared 条目)。
- PrettyUrlCompiler(编译 pattern 为正则)。
- ShortUrlPolicy(短地址策略判断与前缀补回)。
- BackendDeclaredMatcher 依赖:
- RouteManifest(按端过滤 declared 条目)。
- PrettyUrlCompiler(编译 pattern)。
- RouteEntry(endNamespace、acceptsMethod)。
- UrlBuilder 依赖:
- RouteManifest(getRuleGroups 获取模板族)。
- PrettyUrlCompiler(fill 填充路径)。
- RouteRules(选择风格)。
- ShortUrlPolicy(短地址模块行为)。
graph LR
PRM["PrettyRouteMatcher"] --> RM["RouteManifest"]
PRM --> PUC["PrettyUrlCompiler"]
PRM --> SUP["ShortUrlPolicy"]
BDM["BackendDeclaredMatcher"] --> RM
BDM --> PUC
BDM --> RE["RouteEntry"]
UB["UrlBuilder"] --> RM
UB --> PUC
UB --> RR["RouteRules"]
UB --> SUP
图示来源
- PrettyRouteMatcher.php:17-19
- BackendDeclaredMatcher.php:15-25
- UrlBuilder.php:17-20
章节来源
- PrettyRouteMatcher.php:17-19
- BackendDeclaredMatcher.php:15-25
- UrlBuilder.php:17-20
性能考量
- 时间复杂度:
- 匹配阶段:O(N) 遍历 declared 条目 + O(K log K) 排序(K 为命中候选数)。
- 正则匹配由 PCRE 引擎优化,整体可控。
- 空间复杂度:
- 进程级缓存存储 compiled regex、entries、nameIndex、ruleGroups 与字段缓存,需关注内存占用。
- 热点优化:
- 预编译前台规则,减少请求期编译。
- 列表预热批量缓存 slug/time/category_id,降低详情页 IO。
- 短地址策略避免长格式冲突,减少无效匹配。
故障排查指南
- 使用 devtools/url-route-scan.php 进行一致性扫描:
- 检测模板 {url} 旧 key 残留、PHP route() 旧 key 残留、控制器 member 动作旧 key 残留、route(...) . '&' 手工拼接警告。
- 适用于 admin/view、admin/controller、admin/service 范围。
- 诊断步骤:
- 运行扫描脚本,查看 FAIL/WARN 输出。
- 针对 FAIL 项修复旧 key 或拼接方式。
- 对于 WARN 项建议改用 params 数组,提升可读性与可维护性。
章节来源
- url-route-scan.php:1-15
- url-route-scan.php:28-96
- url-route-scan.php:98-149
- url-route-scan.php:151-230
- url-route-scan.php:232-270
结论
DouPHP 的路由系统通过“声明式清单 + 双向编译器 + 多端匹配器”的设计,实现了高可维护性、高性能与强一致性:
- 优先级明确:静态优先、正则精确匹配、通配符有序消歧。
- 前后端解耦:前台 PrettyRouteMatcher 与后台 BackendDeclaredMatcher 同构但隔离。
- 双向可逆:PrettyUrlCompiler 同时支撑入站匹配与出站构建。
- 缓存高效:编译时与运行时多级缓存,配合列表预热显著降低 IO。
- 调试完善:提供一致性扫描工具,便于发现历史遗留问题。
附录
- Pattern 迷你语言要点:
- {name}:默认子模式 [^/]+,可由 params[name] 覆盖。
- {name:regex}:内联正则优先。
- [... ]:可选段,编译为 (?:...)?,填充时按需渲染。
- 短地址策略:
- 启用后禁止带模块前缀的长格式;未命中时补回前缀重试。
- 短地址模块整族选用 column_short,分类段取顶级祖先别名或全链。