文档目录
路由匹配算法

简介

本技术文档聚焦 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,分类段取顶级祖先别名或全链。
添加日期:2026-10-05