简介
本技术文档聚焦 DouPHP 的三端路由解析体系,系统阐述 URL 匹配算法、参数提取、路由优先级判断与委托调度机制。重点覆盖:
- 前台 FrontResolver、后台 AdminResolver、API ApiResolver 的职责与差异
- DelegatingRouter 如何统一协调三端路由请求
- 从 URL 到控制器的完整调用链
- 性能优化策略与调试技巧
- 面向初学者的概念说明与面向高级开发者的自定义解析器扩展指南
项目结构
DouPHP 将路由能力按“端”拆分,每端拥有独立的 Router(薄壳)与 Resolver(解析器),并通过统一的中间件注册表与分发器协作。核心路径如下:
- 前台:front/foundation/routing/*
- 后台:admin/foundation/routing/*
- API:api/foundation/routing/*
- 公共路由基础设施:core/web/routing/*
graph TB
subgraph "前台"
FR["FrontResolver"]
FRouter["Front\\Foundation\\Routing\\Router"]
end
subgraph "后台"
AR["AdminResolver"]
ARouter["Admin\\Foundation\\Routing\\Router"]
end
subgraph "API"
R["ApiResolver"]
RRouter["Api\\Foundation\\Routing\\Router"]
end
subgraph "公共"
DR["DelegatingRouter"]
DP["DispatchPlan"]
PPM["PrettyRouteMatcher"]
BDM["BackendDeclaredMatcher"]
DIS["Dispatcher"]
end
FRouter --> FR
ARouter --> AR
RRouter --> R
FR --> PPM
AR --> BDM
R --> BDM
FR --> DP
AR --> DP
R --> DP
DR --> FRouter
DR --> ARouter
DR --> RRouter
DP --> DIS
图示来源
- front/foundation/routing/Router.php:33-56
- admin/foundation/routing/Router.php:34-64
- api/foundation/routing/Router.php:36-68
- core/web/routing/DelegatingRouter.php:39-67
- core/web/routing/DispatchPlan.php:33-112
- front/foundation/routing/PrettyRouteMatcher.php:44-114
- core/web/routing/BackendDeclaredMatcher.php:38-62
章节来源
- front/foundation/routing/Router.php:33-56
- admin/foundation/routing/Router.php:34-64
- api/foundation/routing/Router.php:36-68
- core/web/routing/DelegatingRouter.php:39-67
核心组件
- 三端 Router(薄壳):负责从容器获取 Request,调用对应 Resolver 生成 DispatchPlan,再交由 Dispatcher 执行;未匹配时返回端专属 404 或重定向。
- 三端 Resolver:实现具体解析逻辑,产出 DispatchPlan,并设置 Request 的路由信息、参数与视图上下文。
- 匹配器:
- PrettyRouteMatcher:前台外观 URL 匹配,基于 RouteManifest 的 declared 条目,支持短地址策略与方法消歧。
- BackendDeclaredMatcher:后台与 API 共用,对 declared 条目进行 specificity 排序与方法过滤。
- 分发计划 DispatchPlan:不可变值对象,承载控制器 FQCN、方法、参数、中间件列表以及 404/405 状态。
- 委托调度 DelegatingRouter:统一入口包装,透传 dispatch(),并提供 current() 读取当前路由状态。
章节来源
- front/foundation/routing/FrontResolver.php:52-106
- admin/foundation/routing/AdminResolver.php:72-104
- api/foundation/routing/ApiResolver.php:60-91
- core/web/routing/DispatchPlan.php:33-112
- core/web/routing/DelegatingRouter.php:39-109
架构总览
三端路由采用“薄壳 Router + 专用 Resolver + 共享匹配器 + 统一分发”的分层设计。请求进入后:
- 端 Router 从容器取出 Request
- 调用 Resolver 解析为 DispatchPlan
- 根据 DispatchPlan 的状态(404/405/正常)决定响应或继续分发
- 通过中央 Dispatcher 在中间件管道中执行控制器
sequenceDiagram
participant C as "客户端"
participant DR as "DelegatingRouter"
participant R as "端 Router"
participant Res as "Resolver"
participant M as "匹配器"
participant DP as "DispatchPlan"
participant D as "Dispatcher"
C->>DR : 发起请求
DR->>R : dispatch()
R->>Res : resolve(request, container)
Res->>M : normalize/match(route, method)
M-->>Res : 命中结果/未命中
Res-->>R : DispatchPlan
alt 404
R-->>C : 端专属 404/重定向
else 405
R-->>C : 405 JSON/Allow 头
else 正常
R->>D : run(plan, container)
D-->>C : Response/null
end
图示来源
- core/web/routing/DelegatingRouter.php:61-67
- front/foundation/routing/Router.php:40-56
- admin/foundation/routing/Router.php:41-64
- api/foundation/routing/Router.php:43-68
- front/foundation/routing/FrontResolver.php:52-106
- admin/foundation/routing/AdminResolver.php:72-104
- api/foundation/routing/ApiResolver.php:60-91
详细组件分析
前台路由解析器 FrontResolver
职责与流程:
- 读取已剥离语言前缀的 routeString
- 使用 PrettyRouteMatcher 规范化匹配,处理首页空串短路
- 校验控制器类存在性,必要时记录警告日志
- 组装中间件栈(默认安全栈 + 路由级细化)
- 设置 Request 的 baseUrl、route/module/action/sub、路由参数与表单目标
- 产出 DispatchPlan 并交由 Dispatcher 执行
URL 匹配与优先级:
- 仅依据 RouteManifest 的前台 declared 条目
- 短地址策略启用时禁止长格式模块前缀,未命中则补回模块前缀重试
- specificity 排序:占位符少者优先 → 字面段多者优先 → 声明顺序兜底
- 方法感知:HEAD 按 GET 处理;无方法白名单视为 permissive
flowchart TD
Start(["入口: FrontResolver::resolve"]) --> Read["读取 routeString 与 HTTP 方法"]
Read --> Normalize["PrettyRouteMatcher::normalize"]
Normalize --> Matched{"是否匹配?"}
Matched -- 否 --> LogWarn["记录未匹配日志"] --> NotFound["返回 notFound 计划"]
Matched -- 是 --> Validate["校验控制器类存在"]
Validate --> |不存在| NotFound
Validate --> |存在| MW["组装中间件栈"]
MW --> SetReq["设置 Request 路由信息与参数"]
SetReq --> Plan["构建 DispatchPlan"]
Plan --> End(["返回计划给 Router"])
图示来源
- front/foundation/routing/FrontResolver.php:52-106
- front/foundation/routing/PrettyRouteMatcher.php:61-114
章节来源
- front/foundation/routing/FrontResolver.php:52-106
- front/foundation/routing/PrettyRouteMatcher.php:61-114
后台路由解析器 AdminResolver
职责与流程:
- 以 ?route=module[/id[/action]] 形态进入
- 使用 BackendDeclaredMatcher 按端命名空间 'Admin' 匹配 declared 条目
- 若命中但方法不被接受,返回 methodNotAllowed 计划
- 设置 Request 路由信息与参数,装配表单目标(create/edit)
- 组装中间件栈(安全头、可信代理、认证、权限、CSRF、工作区)
- 产出 DispatchPlan
sequenceDiagram
participant ARouter as "Admin Router"
participant AR as "AdminResolver"
participant BDM as "BackendDeclaredMatcher"
participant DP as "DispatchPlan"
ARouter->>AR : resolve(request, container)
AR->>BDM : match(routeRaw, 'Admin', method)
alt 未命中
BDM-->>AR : null
AR-->>ARouter : notFound
else 方法不允许
BDM-->>AR : {status : 'method_not_allowed', allow}
AR-->>ARouter : methodNotAllowed
else 命中
BDM-->>AR : {fqcn, action, module, sub, params, entry}
AR->>AR : 设置 Request 路由与参数
AR-->>ARouter : DispatchPlan
end
图示来源
- admin/foundation/routing/AdminResolver.php:72-104
- core/web/routing/BackendDeclaredMatcher.php:49-62
章节来源
- admin/foundation/routing/AdminResolver.php:72-104
- core/web/routing/BackendDeclaredMatcher.php:49-62
API 路由解析器 ApiResolver
职责与流程:
- 与后台相同使用 BackendDeclaredMatcher,但端命名空间为 'Api'
- 未命中返回 notFound(JSON 404);方法不允许返回 methodNotAllowed(JSON 405 + Allow)
- 设置 Request 路由信息与参数,组装中间件栈(安全头、可信代理、限流、可选用户认证)
- 产出 DispatchPlan
classDiagram
class ApiResolver {
+resolve(request, container) DispatchPlan
-defaultAliases() string[]
-aliasMap : array
}
class BackendDeclaredMatcher {
+match(routeRaw, end, httpMethod) array|null
}
class DispatchPlan {
+fqcn
+method
+params
+middlewares
+isNotFound() bool
+isMethodNotAllowed() bool
}
ApiResolver --> BackendDeclaredMatcher : "匹配 declared 条目"
ApiResolver --> DispatchPlan : "产出计划"
图示来源
- api/foundation/routing/ApiResolver.php:60-91
- core/web/routing/BackendDeclaredMatcher.php:49-62
- core/web/routing/DispatchPlan.php:33-112
章节来源
- api/foundation/routing/ApiResolver.php:60-91
- core/web/routing/BackendDeclaredMatcher.php:49-62
委托调度器 DelegatingRouter
职责:
- 作为三端 Router 的统一薄包装,不实现解析与分发
- setDelegate 注入具体端 Router,dispatch() 透传调用
- current() 从容器中读取 Request 的全量路由状态(module/action/sub/route/lang/is_home)
sequenceDiagram
participant Entry as "入口"
participant DR as "DelegatingRouter"
participant FR as "Front Router"
participant AR as "Admin Router"
participant Rr as "Api Router"
Entry->>DR : setDelegate(任一 Router)
Entry->>DR : dispatch()
alt 前台
DR->>FR : dispatch()
FR-->>DR : Response|null
else 后台
DR->>AR : dispatch()
AR-->>DR : Response|null
else API
DR->>Rr : dispatch()
Rr-->>DR : Response|null
end
DR-->>Entry : Response|null
图示来源
- core/web/routing/DelegatingRouter.php:39-67
- core/web/routing/DelegatingRouter.php:79-109
章节来源
- core/web/routing/DelegatingRouter.php:39-109
分发计划 DispatchPlan
作用:
- 不可变值对象,承载最终控制器 FQCN、方法、参数、中间件列表
- 提供 isNotFound()/isMethodNotAllowed() 判定,供各端 Router 渲染端专属错误响应
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
}
图示来源
- core/web/routing/DispatchPlan.php:33-112
章节来源
- core/web/routing/DispatchPlan.php:33-112
匹配器与优先级算法
- PrettyRouteMatcher(前台):
- 首页空串短路至 IndexController
- 短地址策略:禁用带模块前缀的长格式;未命中时补回模块前缀重试
- specificity 排序:占位符少优先 → 字面段多优先 → 声明顺序兜底
- 方法感知:HEAD 按 GET;无 methods 视为 permissive
- BackendDeclaredMatcher(后台/API):
- 按端命名空间过滤 declared 条目
- specificity 排序与方法过滤;URL 命中但方法全不命中返回 method_not_allowed
- 空 routeRaw 时 fallback 到 index
flowchart TD
A["收集全部命中规则"] --> B["specificity 排序"]
B --> C{"是否传入 HTTP 方法?"}
C -- 否 --> D["返回首条候选"]
C -- 是 --> E{"首个方法命中?"}
E -- 是 --> F["返回命中"]
E -- 否 --> G{"是否还有候选?"}
G -- 是 --> E
G -- 否 --> H["返回 method_not_allowed"]
图示来源
- front/foundation/routing/PrettyRouteMatcher.php:117-169
- core/web/routing/BackendDeclaredMatcher.php:72-126
章节来源
- front/foundation/routing/PrettyRouteMatcher.php:117-169
- core/web/routing/BackendDeclaredMatcher.php:72-126
依赖关系分析
- 三端 Router 依赖各自 Resolver
- Resolver 依赖匹配器(前台 PrettyRouteMatcher,后台/API BackendDeclaredMatcher)
- 所有 Resolver 产出 DispatchPlan,并由 Dispatcher 执行
- DelegatingRouter 统一代理三端 Router 的 dispatch()
- 中间件通过 MiddlewareRegistry 组合,受路由条目配置影响
graph LR
RFront["Front Router"] --> FR["FrontResolver"]
RAdmin["Admin Router"] --> AR["AdminResolver"]
RApi["Api Router"] --> ARi["ApiResolver"]
FR --> PPM["PrettyRouteMatcher"]
AR --> BDM["BackendDeclaredMatcher"]
ARi --> BDM
FR --> DP["DispatchPlan"]
AR --> DP
ARi --> DP
DP --> DIS["Dispatcher"]
DR["DelegatingRouter"] --> RFront
DR --> RAdmin
DR --> RApi
图示来源
- front/foundation/routing/Router.php:40-56
- admin/foundation/routing/Router.php:41-64
- api/foundation/routing/Router.php:43-68
- core/web/routing/DelegatingRouter.php:61-67
章节来源
- front/foundation/routing/Router.php:40-56
- admin/foundation/routing/Router.php:41-64
- api/foundation/routing/Router.php:43-68
- core/web/routing/DelegatingRouter.php:61-67
性能考量
- 预编译正则:匹配器在加载阶段将 pattern 编译为 PCRE,避免重复编译开销
- specificity 排序:减少回溯与误匹配,提升命中率与稳定性
- 短地址策略:减少 URL 长度与解析分支,同时保证可逆
- 方法感知:尽早过滤不合法方法,降低后续处理成本
- 中间件按需组合:路由条目可跳过或追加中间件,避免不必要的检查
- 日志定位:未匹配与控制器缺失记录警告日志,便于快速定位问题
故障排查指南
- 前台未匹配:
- 检查 routeString 是否已剥离语言前缀
- 确认 PrettyRouteMatcher 的 declared 条目是否存在且 pattern 正确
- 查看日志中的“Front route unmatched”与“Front route dispatch failed”
- 后台/API 未匹配或 405:
- 确认 BackendDeclaredMatcher 的端命名空间与 declared 条目
- 检查 HTTP 方法是否在白名单内;若 URL 命中但方法不匹配,会返回 method_not_allowed
- 控制器缺失:
- FrontResolver 会在控制器类不存在时记录警告并返回 notFound
- 中间件问题:
- 通过路由条目的 mw_without/mw_append/mw_params 调整中间件行为
- 确保别名映射正确,缺失类会被 Registry 吞掉跳过
章节来源
- front/foundation/routing/FrontResolver.php:58-86
- admin/foundation/routing/AdminResolver.php:76-82
- api/foundation/routing/ApiResolver.php:64-70
结论
DouPHP 的路由体系以“端隔离、声明式、可组合”为核心思想:
- 三端 Router 保持薄壳,解析逻辑集中在 Resolver
- 匹配器统一遵循 specificity 与方法感知,保证一致性与可预测性
- DispatchPlan 作为不可变契约,简化错误处理与分发流程
- DelegatingRouter 提供统一入口与当前路由状态读取,便于全局初始化与诊断 该设计既满足初学者理解路由基本概念,也为高级开发者提供了清晰的扩展点(如自定义匹配器、中间件与路由条目)。
附录
- 自定义解析器开发指南(高级)
- 实现端 Resolver:遵循 resolve(request, container) 签名,产出 DispatchPlan
- 复用匹配器:前台使用 PrettyRouteMatcher,后台/API 使用 BackendDeclaredMatcher
- 中间件组合:通过 MiddlewareRegistry 与别名映射,结合路由条目配置
- 错误处理:返回 DispatchPlan::notFound() 或 methodNotAllowed(),由各端 Router 渲染端专属响应
- 测试建议:覆盖未匹配、方法不允许、控制器缺失等边界场景