文档目录
路由匹配机制

简介

本文面向 DouPHP 框架的路由匹配机制,系统性阐述 Route 类的声明式路由注册能力、前端 PrettyRouteMatcher 的匹配算法与参数提取、DispatchPlan 的分发计划结构与控制器绑定、MethodResolver 的方法解析策略,以及 Dispatcher 在中间件管道中的执行流程。同时给出路由系统与 MVC 的集成方式、扩展自定义规则的建议,并总结性能优化与调试方法。

项目结构

DouPHP 将“声明式路由”与“请求期匹配/分发”解耦:

  • 构建期(manifest):通过 Route 门面在 front/admin/api 的 route/*.php 中声明路由条目,最终汇聚为 RouteManifest。
  • 运行期:各端 Resolver(Front/Admin/Api)读取 Manifest,使用对应匹配器进行 URL 匹配,产出 DispatchPlan,再由中央 Dispatcher 执行。
graph TB
subgraph "构建期"
R["Route 门面<br/>声明式 fluent API"] --> M["RouteManifest<br/>集中存储 declared 条目"]
end
subgraph "前台"
FR["FrontResolver"] --> PRM["PrettyRouteMatcher"]
PRM --> M
FR --> DP["DispatchPlan"]
end
subgraph "后台"
AR["AdminResolver"] --> BDM["BackendDeclaredMatcher"]
BDM --> M
AR --> DP
end
subgraph "API"
AAR["ApiResolver"] --> ADM["BackendDeclaredMatcher"]
ADM --> M
AAR --> DP
end
DP --> D["Dispatcher<br/>中间件管道 + 控制器调用"]

核心组件

  • Route:声明式路由 fluent API 的门面,提供 get/post/put/patch/delete/match/any/group/resource/column/simple/page 等入口,用于在 manifest 构建期累积 RouteEntry。
  • PrettyRouteMatcher:前台外观 URL 匹配器,基于 Manifest 的 declared 条目进行正则匹配、specificity 排序与方法白名单过滤,输出标准化路由信息。
  • FrontResolver:前台端 Resolver,组合 PrettyRouteMatcher 结果、组装中间件链、写入 Request 路由上下文,并产出 DispatchPlan。
  • AdminResolver/ApiResolver:后台/API 端 Resolver,基于 BackendDeclaredMatcher 对 declared 条目进行匹配与 405 判定,产出 DispatchPlan。
  • MethodResolver:路径动作段到控制器方法的解析器,支持横线转驼峰、保留字映射、多形态候选匹配与兜底 index。
  • DispatchPlan:不可变值对象,承载 fqcn/method/params/middlewares,并提供 notFound()/methodNotAllowed() 工厂方法与状态判断。
  • Dispatcher:中央分发器,在中间件管道中懒实例化控制器并调用方法,返回控制器返回值或 Response。

架构总览

下图展示从请求进入至控制器执行的完整链路,涵盖前端、后台、API 三端的 Resolver 与统一的 Dispatcher。

sequenceDiagram
participant C as "客户端"
participant FR as "FrontResolver"
participant PRM as "PrettyRouteMatcher"
participant AR as "AdminResolver"
participant AAR as "ApiResolver"
participant DP as "DispatchPlan"
participant D as "Dispatcher"
C->>FR : 前台请求
FR->>PRM : normalize(route, method)
PRM-->>FR : {matched,module,target,controller,action,sub,params,...}
FR->>DP : new DispatchPlan(fqcn, method, params, middlewares)
FR->>D : run(plan, container)
D-->>C : 响应或null(交由端Router处理404)
C->>AR : 后台请求
AR->>AR : BackendDeclaredMatcher.match(...)
AR-->>DP : notFound / methodNotAllowed / plan
AR->>D : run(plan, container)
C->>AAR : API请求
AAR->>AAR : BackendDeclaredMatcher.match(...)
AAR-->>DP : notFound / methodNotAllowed / plan
AAR->>D : run(plan, container)

详细组件分析

Route:声明式路由注册

  • 作用:仅在 manifest 构建期使用,提供 fluent API 将 pattern、controller、action、module、name、params、sub、middleware 等信息收集为 RouteEntry,最终写入 RouteManifest。
  • 关键能力:
    • 单条动词路由:get/post/put/patch/delete/match/any
    • 分组与资源:group 批量声明;resource 展开标准 CRUD 集(index/create/store/edit/update/destroy),支持 only/except/extras
    • 风格化展开:column/simple/page 按当前选中风格规则批量生成 declared 条目
  • 设计要点:
    • 通过 useCollector 安装 RouteCollector,避免跨请求污染
    • makeEntry 统一构造 RouteEntryBuilder,自动处理 params/sub 默认值
    • column/simple/page 借助 StyleRuleExpander 将 meta 模板转为具体 declared 条目

PrettyRouteMatcher:前台匹配算法与参数提取

  • 输入:已去除语言前缀的路由字符串与 HTTP 方法
  • 处理流程:
    • 空串短路:首页直接命中 IndexController
    • 短地址策略:若启用短地址且传入长格式则拒绝;未命中时尝试补回模块前缀重试
    • 规则匹配:遍历预编译的 declared 规则(含 _regex),收集全部命中项
    • specificity 排序:占位符少者优先 → 字面段多者优先 → 声明顺序兜底
    • 方法白名单:按 methods 过滤(HEAD 视为 GET)
    • 结果构建:提取显式 controller/module/action/target/sub/name,具名捕获组作为 params
  • 参数提取:仅保留具名且非空的捕获组,排除 module/action/sub_action 等内部字段
flowchart TD
Start(["开始"]) --> Trim["清理首尾斜杠"]
Trim --> Blank{"是否空串?"}
Blank -- 是 --> Home["返回首页IndexController"]
Blank -- 否 --> ShortCheck{"短地址策略拦截?"}
ShortCheck -- 是 --> Fail["返回未匹配"]
ShortCheck -- 否 --> Match["遍历规则正则匹配"]
Match --> AnyHit{"有命中?"}
AnyHit -- 否 --> Retry{"短地址补前缀重试?"}
Retry -- 是 --> MatchRetry["用带前缀路径再匹配"]
Retry -- 否 --> Fail
MatchRetry --> AnyHit
AnyHit -- 是 --> Sort["specificity 排序"]
Sort --> Filter{"HTTP方法白名单"}
Filter --> Build["构建标准化结果<br/>module/target/controller/action/sub/name/params"]
Build --> End(["结束"])

FrontResolver:前台端解析与中间件装配

  • 职责:
    • 调用 PrettyRouteMatcher 获取标准化路由信息
    • 校验控制器类存在性,必要时记录日志并返回 notFound
    • 根据配置与命中条目装配中间件链(安全头、可信代理、限流、用户认证、CSRF)
    • 设置 Request 基础信息(baseUrl、routeModule/action/sub、params、视图 cur)
    • 调用 MethodResolver 解析方法,产出 DispatchPlan
  • 表单目标装配:create/edit 场景下自动注入 form_action/form_method,便于模板渲染

AdminResolver/ApiResolver:后台与API端解析

  • 共同点:
    • 使用 BackendDeclaredMatcher 对 declared 条目进行匹配
    • 支持 405 判定(URL 命中但方法不被接受)
    • 装配中间件链(secure-by-default 默认栈 + 路由级细化)
    • 产出 DispatchPlan(fqcn/method/params/middlewares)
  • 差异点:
    • 后台默认中间件包含 auth/permission/workspace 等管理端能力
    • API 默认中间件包含 security_headers/trust_proxy/throttle/user_auth 等接口保护能力

MethodResolver:控制器方法解析

  • 输入:路径动作段(pathAction)与控制器 FQCN
  • 解析策略:
    • 横线转驼峰:custom-admin-path → customAdminPath
    • 保留字别名映射:default → index,list → listing,use/class 特殊处理
    • 候选方法集:按 snake_case/camelCase/原样/小写等多形态生成候选,依次检查 method_exists
    • 兜底:若 pathActionLower 合法且控制器存在同名方法,直接使用;否则回退 index
  • 命名约定:支持 snake_case、camelCase、横线分隔等多种风格
flowchart TD
S["开始"] --> T1["横线转驼峰"]
T1 --> Map{"保留字映射?"}
Map -- 是 --> UseMap["使用映射结果"]
Map -- 否 --> Gen["生成候选方法集"]
Gen --> Check{"method_exists 命中?"}
Check -- 是 --> ReturnCand["返回候选方法"]
Check -- 否 --> Fallback{"合法标识且存在同名方法?"}
Fallback -- 是 --> ReturnSame["返回同名方法"]
Fallback -- 否 --> Default["回退到 index"]
UseMap --> End(["结束"])
ReturnCand --> End
ReturnSame --> End
Default --> End

DispatchPlan:分发计划结构

  • 属性:
    • fqcn:最终控制器全限定类名(null 表示未匹配)
    • method:最终控制器方法名
    • params:路径参数(已写入 Request 独立路由袋)
    • middlewares:中间件实例列表
    • methodNotAllowed:是否 405
    • allowedMethods:405 时该 URL 支持的方法集合
  • 工厂方法:
    • notFound():返回未匹配计划
    • methodNotAllowed(allow):返回方法不允许计划
  • 状态判断:
    • isNotFound():优先于 isMethodNotAllowed() 判断
    • isMethodNotAllowed():URL 命中但方法不在白名单
classDiagram
class DispatchPlan {
+string|null fqcn
+string|null method
+array params
+array middlewares
+bool methodNotAllowed
+string[] allowedMethods
+__construct(fqcn, method, params, middlewares)
+static notFound() DispatchPlan
+static methodNotAllowed(allow) DispatchPlan
+isNotFound() bool
+isMethodNotAllowed() bool
}

Dispatcher:控制器调用与中间件管道

  • 职责:
    • 接收 DispatchPlan,若未匹配直接返回 null(由各端 Router 渲染 404)
    • 在中间件管道之前注入 Request 路由参数,确保中间件与控制器均可读取
    • 通过容器懒实例化控制器,并以 method 名称调用,传递 __scene 参数
  • 返回值:控制器返回值(常为 null 或 Response)

路由系统与 MVC 的集成

  • 入口层:
    • 前台 Router 调用 FrontResolver 解析,未匹配时渲染 page_wrong
    • 后台/API 各自 Resolver 产出 DispatchPlan,交由 Dispatcher 执行
  • 控制器绑定:
    • 前端通过 PrettyRouteMatcher 的 _explicit_controller 直接绑定控制器 FQCN
    • 后台/API 通过 BackendDeclaredMatcher 的匹配结果确定 fqcn
  • 方法解析:
    • 统一由 MethodResolver 将路径动作段解析为控制器方法名
  • 参数注入:
    • 路径参数经 Request::setRouteParams/mergeRouteInputs 注入,供中间件与控制器读取
  • 视图集成:
    • FrontResolver/AdminResolver 设置 View::assign('cur', module),并在 create/edit 场景注入 form_action/form_method

依赖关系分析

  • 构建期依赖:
    • Route 门面依赖 RouteCollector/RouteEntryBuilder/StyleRuleExpander 生成 declared 条目
    • StyleRuleExpander 依赖 RouteRules 获取当前选中风格规则
  • 运行期依赖:
    • FrontResolver 依赖 PrettyRouteMatcher、MethodResolver、MiddlewareRegistry
    • AdminResolver/ApiResolver 依赖 BackendDeclaredMatcher、MethodResolver、MiddlewareRegistry
    • Dispatcher 依赖 MiddlewarePipeline、Container
  • 出站 URL 构建:
    • UrlBuilder 与 PrettyRouteMatcher 共用 PrettyUrlCompiler,保证入站/出站双向可逆
graph LR
Route["Route 门面"] --> Expander["StyleRuleExpander"]
Expander --> Rules["RouteRules"]
FR["FrontResolver"] --> PRM["PrettyRouteMatcher"]
FR --> MR["MethodResolver"]
FR --> MW["MiddlewareRegistry"]
AR["AdminResolver"] --> BDM["BackendDeclaredMatcher"]
AR --> MR
AR --> MW
AAR["ApiResolver"] --> BDM
AAR --> MR
AAR --> MW
DP["DispatchPlan"] --> D["Dispatcher"]
D --> MP["MiddlewarePipeline"]
UB["UrlBuilder"] --> PUC["PrettyUrlCompiler"]
PRM --> PUC

性能与优化

  • 预编译正则:
    • PrettyRouteMatcher 在构造时加载 Manifest 并将 pattern 编译为 _regex,避免每次请求重复编译
    • UrlBuilder 与 PrettyRouteMatcher 共用同一编译器,保证一致性与可维护性
  • Specificity 排序:
    • 通过占位符数量与字面段数量排序,减少无效匹配次数,提升命中率
  • 短地址策略:
    • 启用后省略模块名段,减少 URL 长度与匹配复杂度;未命中时补前缀重试,保持可逆
  • 方法白名单过滤:
    • 在匹配阶段即按 methods 过滤,避免后续错误分支
  • 缓存建议:
    • Manifest 构建产物应缓存(如文件缓存或内存缓存),避免每次请求重建
    • 中间件链可按路由名缓存,减少 compose 开销
  • 调试工具:
    • 前台未匹配时记录日志(channel=route),包含 route/lang/is_home 等上下文
    • 可使用 permissive 模式(不传 httpMethod)查看排序后首条命中规则,辅助诊断

故障排查指南

  • 404 未匹配:
    • 检查 PrettyRouteMatcher.normalize 是否返回 matched=false
    • 确认短地址策略是否拦截了长格式 URL
    • 查看 FrontResolver 日志(channel=route)中的 route/lang/is_home
  • 405 方法不允许:
    • 检查 declared 条目的 methods 白名单是否与请求方法匹配
    • 后台/API Resolver 会返回 methodNotAllowed,需检查 Allow 头
  • 控制器未找到:
    • 确认 _explicit_controller 指向的类是否存在
    • 检查 Module::assertUserAvailable 是否阻断 user 模块衍生
  • 中间件问题:
    • 检查 MiddlewareRegistry 的默认栈与路由级 without/append/params 配置
    • 确认 skip_all_middleware 是否正确跳过全局栈
  • 表单目标异常:
    • 确认 create/edit 场景下 name 是否为资源路由名(含点分)
    • 检查 route() 生成的 base 是否正确

结论

DouPHP 的路由系统以“声明式 + 预编译 + 三段式 Resolver”为核心,实现了高内聚、低耦合的路由匹配与分发。Route 门面负责构建期声明,PrettyRouteMatcher/BackendDeclaredMatcher 负责运行期匹配,MethodResolver 统一方法解析,DispatchPlan 作为不可变契约贯穿全流程,Dispatcher 在中间件管道中完成控制器调用。通过 specificity 排序、方法白名单、短地址策略与日志记录,系统在性能与可观测性上达到良好平衡。扩展自定义规则时,建议在 manifest 构建期新增 declared 条目,并在对应 Resolver 中复用现有匹配与中间件装配逻辑。

附录

  • 扩展自定义路由匹配规则的建议:
    • 在 front/admin/api 的 route/*.php 中使用 Route::get/post/... 或 resource/group 声明新条目
    • 如需复杂规则,可在 StyleRuleExpander 中新增风格规则族,并通过 Route::column/simple/page 展开
    • 对于 API/后台,确保 BackendDeclaredMatcher 能识别新增的 declared 条目
    • 结合 MiddlewareRegistry 的路由级细化(without/append/params/skip_all)实现细粒度控制
  • 与 MVC 的集成要点:
    • 控制器 FQCN 由 declared 条目显式指定,避免约定式发现的歧义
    • 方法名由 MethodResolver 统一解析,支持多种命名风格
    • 参数通过 Request 路由袋注入,中间件与控制器均可访问
    • 视图通过 View::assign 注入 cur、form_action/form_method 等上下文
添加日期:2026-10-05