简介
本文面向 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 等上下文