简介
本技术文档聚焦于 DouPHP 核心路由构建器,围绕 RouteBuilder 体系(以 Route、RouteEntryBuilder、RouteResourceBuilder、RouteGroupBuilder 为核心)展开,系统阐述其设计理念、链式调用模式、方法解析机制、路由元数据管理(名称、描述、中间件绑定、导航元数据等)、以及匹配算法与性能优化策略。文档同时提供从入门到进阶的学习路径、常见问题的解决方案与扩展指南,帮助开发者高效使用并安全扩展路由能力。
更新 本次更新重点介绍了新增的 RouteNavDsl 特性,该特性允许在路由构建过程中传递后台导航元数据,支持在 Request 中获取命中的路由条目对象。
项目结构
核心路由构建器位于 core/web/routing 目录下,采用"门面 + Builder + 值对象"的分层设计:
- 门面:Route,暴露声明式 fluent API(get/post/put/patch/delete/match/any/group/resource/column/simple/page)。
- 构建器:RouteEntryBuilder(单条路由)、RouteResourceBuilder(CRUD 资源批量展开)、RouteGroupBuilder(分组路由)。
- 值对象:RouteEntry(不可变的路由清单条目,承载 pattern、params、methods、middleware、nav 等元数据)。
- 横切能力:RouteMiddlewareDsl(中间件 DSL)、RouteNavDsl(导航元数据 DSL)、RouteNameResolution(路由名解析与 compositeModule 支持)。
- 辅助工具:JsRouteBuilder(浏览器端 route.js 的 PHP 镜像,用于 URL 生成与占位符处理)。
graph TB
A["Route<br/>声明式门面"] --> B["RouteEntryBuilder<br/>单条路由构建"]
A --> C["RouteResourceBuilder<br/>CRUD 资源批量展开"]
A --> D["RouteGroupBuilder<br/>分组路由构建"]
B --> E["RouteEntry<br/>路由条目值对象"]
C --> E
D --> E
B --> F["RouteMiddlewareDsl<br/>中间件 DSL"]
C --> F
D --> F
B --> G["RouteNavDsl<br/>导航元数据 DSL"]
C --> G
D --> G
B --> H["RouteNameResolution<br/>路由名解析"]
C --> H
D --> H
I["JsRouteBuilder<br/>URL 生成镜像"] -.-> E
J["Request<br/>请求上下文"] --> E
图示来源
- Route.php:21-129
- RouteEntryBuilder.php:21-80
- RouteResourceBuilder.php:21-123
- RouteGroupBuilder.php:43-47
- RouteEntry.php:21-93
- RouteMiddlewareDsl.php:21-44
- RouteNavDsl.php:37-63
- RouteNameResolution.php:21-39
- JsRouteBuilder.php:23-40
- Request.php:1275-1321
核心组件
- Route(门面):仅构建期使用,安装累积器并提供 get/post/put/patch/delete/match/any/group/resource/column/simple/page 等入口,统一创建 Builder 并将结果推入当前累积器。
- RouteEntryBuilder:单条路由的链式构建器,承载 pattern、controller/action、module/sub、HTTP 方法白名单、参数映射、中间件 DSL、导航元数据 DSL、路由名解析;在 register() 或析构时生成 RouteEntry 并 push。
- RouteResourceBuilder:CRUD 资源批量展开,基于内置 resourceMap 将标准动作映射为 pattern 与 methods,支持 only/except/extras(动词方法追加),同样输出 RouteEntry。
- RouteGroupBuilder:分组路由构建器,支持按动词分桶注册多个 action,最终展开为多条 RouteEntry。
- RouteEntry:不可变值对象,规范化字段契约,包含 name、route_type、pattern、params、methods、middleware、nav、without_middleware、middleware_params、skip_all_middleware、is_short_url_aware、is_family、source 等。
- RouteMiddlewareDsl:提供 middleware/withoutMiddleware/skipAllMiddleware/permission/throttle/auth 等 DSL,汇总为 fields 写入每条 RouteEntry。
- RouteNavDsl:提供 nav() 方法设置后台导航元数据,支持 cur/group/sub_cur 等键,汇总为 navFields() 写入每条 RouteEntry。
- RouteNameResolution:负责 name 前缀叠加、compositeModule 生效逻辑、actionTakesBaseName 判定与 qualifyName 幂等拼接。
- JsRouteBuilder:浏览器端 route.js 的 PHP 镜像,实现 url() 填充占位符、重写模式、前台语言前缀、查询参数拼装等。
更新 新增了 RouteGroupBuilder 和 RouteNavDsl 组件,增强了路由构建器的导航元数据支持能力。
架构总览
构建期流程:
- RouteManifestBuilder 在安装每个 route 文件前通过 Route::useCollector 安装 RouteCollector。
- 路由声明文件中调用 Route 的 fluent API(如 get/post/resource/group),内部创建对应 Builder。
- Builder 收集配置(pattern、methods、params、sub、name、中间件、导航元数据等),最终生成 RouteEntry 并 push 到 collector。
- 运行期匹配器读取 manifest(由 RouteEntry 组成)进行匹配与分发,并通过 Request::setRoute() 注入命中的路由条目。
sequenceDiagram
participant MB as "RouteManifestBuilder"
participant R as "Route(门面)"
participant EB as "RouteEntryBuilder"
participant RB as "RouteResourceBuilder"
participant GB as "RouteGroupBuilder"
participant C as "RouteCollector"
participant E as "RouteEntry"
participant Req as "Request"
MB->>R : useCollector(collector)
Note over MB,R : 安装累积器,进入 include 周期
R->>EB : get/post/... (pattern, controller, action, module, name, params, sub)
EB->>EB : 链式设置 name/params/sub/middleware/nav
EB->>C : push(new RouteEntry(fields))
R->>RB : resource(module, controller)
RB->>RB : prefix/sub/only/except/get/post/...
RB->>C : push(new RouteEntry(fields))
R->>GB : group(module, controller)
GB->>GB : prefix/sub/name/compositeModule/nav
GB->>C : push(new RouteEntry(fields))
Note over MB,C : 所有 declared 条目汇聚至 manifest
Note over Req,E : 运行期通过 setRoute() 注入命中的 RouteEntry
图示来源
- Route.php:21-129
- RouteEntryBuilder.php:129-173
- RouteResourceBuilder.php:299-343
- RouteGroupBuilder.php:270-325
- RouteEntry.php:94-119
- Request.php:1275-1321
详细组件分析
Route(声明式门面)
- 职责:提供统一的声明式入口,屏蔽底层 Builder 差异;仅在构建期使用,不持有请求上下文。
- 关键能力:
- 动词入口:get/post/put/patch/delete/match/any,内部统一调用 makeEntry 创建 RouteEntryBuilder。
- 分组与资源:group 返回 RouteGroupBuilder(逐 action 枚举),resource 返回 RouteResourceBuilder(CRUD 批量展开)。
- 风格化展开:column/simple/page 根据当前选中风格规则展开为多条 declared 条目。
- 组级前缀:name/compositeModule 开启组级注册器,叠加 name 前缀与复合模块标记。
classDiagram
class Route {
+useCollector(collector) void
+collector() RouteCollector
+name(prefix) RouteGroupRegistrar
+compositeModule() RouteGroupRegistrar
+group(module, controller) RouteGroupBuilder
+resource(module, controller) RouteResourceBuilder
+get(...) RouteEntryBuilder
+post(...) RouteEntryBuilder
+put(...) RouteEntryBuilder
+patch(...) RouteEntryBuilder
+delete(...) RouteEntryBuilder
+match(methods,...) RouteEntryBuilder
+any(...) RouteEntryBuilder
+column(module, controller) void
+simple(module, controller) void
+page(controller) void
}
图示来源
- Route.php:21-129
RouteEntryBuilder(单条路由构建器)
- 职责:承载单条路由的全部配置,并在 register()/析构时产出 RouteEntry。
- 关键能力:
- 链式设置:name、params、sub、middleware/withoutMiddleware/skipAllMiddleware/permission/throttle/auth、nav。
- 路由名解析:结合组栈前缀与默认 localBase 计算最终 name。
- 字段组装:构造 fields(含 name、route_type、pattern、params、module、controller、action、sub、methods、is_short_url_aware、is_family、source),合并中间件字段和导航字段后 push。
flowchart TD
Start(["register()"]) --> CheckReg{"已注册?"}
CheckReg --> |是| End(["结束"])
CheckReg --> |否| ComputeName["计算默认localBase与最终name"]
ComputeName --> BuildFields["组装fields(含中间件字段和导航字段)"]
BuildFields --> Push["push(RouteEntry)"]
Push --> End
图示来源
- RouteEntryBuilder.php:129-173
- RouteNameResolution.php:41-61
- RouteMiddlewareDsl.php:128-143
- RouteNavDsl.php:59-62
RouteResourceBuilder(CRUD 资源批量展开)
- 职责:按 RESTful 约定将标准 CRUD 动作映射为 pattern 与 HTTP 方法,并支持 extras。
- 关键能力:
- 标准动作集:index/create/store/edit/update/destroy(show 需显式 only 开启)。
- 资源映射表:resourceMap 定义各动作的 suffix、methods、needs_id。
- 键名与约束:key(name, pattern) 覆盖成员动作路径参数名与正则约束。
- 过滤与扩展:only/except 控制标准集;get/post/put/patch/delete/any 追加 extras。
- 注册:resolveStandardActions + resolveExtraActions 生成 entries 并 push。
- 导航元数据:通过 navFields() 将导航元数据注入每条展开的 RouteEntry。
classDiagram
class RouteResourceBuilder {
-defaultActions : string[]
-resourceMap : array
-prefix : string
-sub : string?
-keyName : string
-keyPattern : string
-onlyList : string[]?
-exceptList : string[]
-extraBuckets : array
+prefix(p) self
+sub(s) self
+name(n) self
+compositeModule() self
+key(name, pattern) self
+only(actions) self
+except(actions) self
+get(actions) self
+post(actions) self
+put(actions) self
+patch(actions) self
+delete(actions) self
+any(actions) self
+nav(nav) self
+register() void
}
图示来源
- RouteResourceBuilder.php:48-123
- RouteResourceBuilder.php:125-296
- RouteResourceBuilder.php:299-343
- RouteNavDsl.php:37-63
RouteGroupBuilder(分组路由构建器)
- 职责:分组路由构建器,支持按动词分桶注册多个 action,最终展开为多条 RouteEntry。
- 关键能力:
- 动词分桶:get/post/put/patch/delete/any 将 actions 按动词分类存储。
- 分组配置:prefix/sub/name/compositeModule/root 设置分组属性。
- 导航元数据:通过 navFields() 将导航元数据注入每条展开的 RouteEntry。
- 批量展开:flush() 方法将所有分桶展开为 RouteEntry 并 push。
classDiagram
class RouteGroupBuilder {
-collector : RouteCollector
-module : string
-controller : string
-prefix : string?
-sub : string?
-rootActionOverride : string?
-buckets : array
-registered : bool
+prefix(p) self
+sub(s) self
+name(n) self
+compositeModule() self
+root(action) self
+get(actions) self
+post(actions) self
+put(actions) self
+patch(actions) self
+delete(actions) self
+any(actions) self
+nav(nav) self
+__destruct() void
+flush() void
}
图示来源
- RouteGroupBuilder.php:43-47
- RouteGroupBuilder.php:49-82
- RouteGroupBuilder.php:270-325
- RouteNavDsl.php:37-63
RouteEntry(路由条目值对象)
- 职责:不可变值对象,承载路由清单条目全部字段,提供 acceptsMethod、toRuleArray、toArray 等方法。
- 关键字段:
- 标识:name、route_type、source
- 匹配:pattern、params、methods、is_short_url_aware、is_family
- 目标:target、module_fixed、module、controller、action、sub
- 中间件:middleware、without_middleware、middleware_params、skip_all_middleware
- 新增 导航元数据:nav(后台导航高亮归属)
classDiagram
class RouteEntry {
+name : string?
+route_type : string
+pattern : string
+params : array
+target : string?
+module_fixed : string?
+module : string?
+controller : string?
+action : string?
+sub : string?
+middleware : array
+methods : array
+without_middleware : array
+middleware_params : array
+skip_all_middleware : bool
+nav : array
+is_short_url_aware : bool
+is_family : bool
+source : string
+acceptsMethod(httpMethod) bool
+toRuleArray() array
+toArray() array
}
图示来源
- RouteEntry.php:21-93
- RouteEntry.php:94-119
- RouteEntry.php:198-236
中间件 DSL(RouteMiddlewareDsl)
- 职责:为各 Builder 提供统一的中间件 DSL,支持追加、豁免、参数覆盖、跳全链。
- 关键语义:
- middleware([...]):在默认栈末尾追加别名(可带参数串)。
- withoutMiddleware([...]):从默认栈中按别名过滤。
- permission/throttle/auth:参数糖,覆盖默认栈中对应中间件的参数。
- skipAllMiddleware():最高优先级,装空链。
- middlewareFields():汇总为 fields 写入每条 RouteEntry。
导航元数据 DSL(RouteNavDsl)
- 职责:为各 Builder 提供统一的导航元数据 DSL,支持后台导航高亮归属声明。
- 关键语义:
- nav(array $nav):声明本路由(或本路由组展开出的每条路由)的后台导航元数据。
- 值形态:cur/group/sub_cur => 标量或 action 级映射。
- navFields():汇总导航相关字段,供 builder 写入每条展开的 RouteEntry。
- 未声明的键由消费端走约定兜底:cur = pattern 一级路由段,group = 条目 module 复合名,sub_cur = 空串。
新增 这是本次更新的核心功能,允许在路由构建过程中传递后台导航元数据。
路由名解析(RouteNameResolution)
- 职责:处理 name 前缀叠加、compositeModule 生效、action 是否取 base name。
- 关键逻辑:
- resolveDeclaredName:组合组栈前缀与单条 name,尾点表示前缀叠加,否则全量覆盖。
- resolveEmittedModule:当 compositeModule 且 sub 非空时,emit 的 module 为 module_sub。
- actionTakesBaseName:根动作或 index 取裸 nameBase,其余为 nameBase.action。
- qualifyName:幂等拼接,避免重复前缀。
JsRouteBuilder(前端 URL 生成镜像)
- 职责:与浏览器端 route.js 保持语义一致的 PHP 镜像,用于离线断言与 roundtrip 扫描。
- 关键能力:
- url(payload, name, params, options):根据路由名与 pattern 填充具名参数,处理 rewrite、前台语言前缀、查询参数与分页。
- placeholderNames(pattern):提取 pattern 中的占位符名列表。
- applyFrontLanguagePrefix:对齐 UrlBuilder 的前台语言前缀行为。
Request 类增强(路由条目访问)
- 职责:请求上下文对象,新增对命中路由条目的支持。
- 关键能力:
- setRoute($module, $action, $sub = '', $entry = null):新增第四个参数 $entry 用于携带命中的 RouteEntry 对象。
- routeEntry():返回当前命中的路由条目(未命中或非声明式路由时为 null)。
- 其他路由相关方法:routeModule()、routeAction()、routeSub()、routeParams() 等保持不变。
新增 这是本次更新的另一个重要功能,允许在控制器和中间件中直接访问命中的路由条目对象。
依赖关系分析
- Route 依赖 RouteCollector(通过静态门面注入),并创建 RouteEntryBuilder / RouteResourceBuilder / RouteGroupBuilder。
- RouteEntryBuilder / RouteResourceBuilder / RouteGroupBuilder 均依赖 RouteMiddlewareDsl、RouteNavDsl 与 RouteNameResolution。
- 三者最终都产出 RouteEntry,并通过 RouteCollector 推入 manifest。
- JsRouteBuilder 依赖 PrettyUrlCompiler(通过 fill 填充 pattern)与 Util(语言前缀处理)。
- Request 类现在可以持有 RouteEntry 对象,通过 setRoute() 的第四个参数注入。
graph LR
Route --> EntryBuilder
Route --> ResourceBuilder
Route --> GroupBuilder
EntryBuilder --> MiddlewareDSL
EntryBuilder --> NavDSL
EntryBuilder --> NameRes
ResourceBuilder --> MiddlewareDSL
ResourceBuilder --> NavDSL
ResourceBuilder --> NameRes
GroupBuilder --> MiddlewareDSL
GroupBuilder --> NavDSL
GroupBuilder --> NameRes
EntryBuilder --> Entry
ResourceBuilder --> Entry
GroupBuilder --> Entry
JsRouteBuilder --> PrettyUrlCompiler
Request --> Entry
图示来源
- Route.php:21-129
- RouteEntryBuilder.php:21-80
- RouteResourceBuilder.php:21-123
- RouteGroupBuilder.php:43-47
- RouteEntry.php:21-93
- RouteMiddlewareDsl.php:21-44
- RouteNavDsl.php:37-63
- RouteNameResolution.php:21-39
- JsRouteBuilder.php:23-40
- Request.php:1275-1321
性能考量
- 构建期一次性展开:resource 的标准动作集与 extras 在 register() 内集中解析,避免运行时重复计算。
- 方法白名单最小化:尽量使用具体动词入口(get/post/...),减少 any() 的使用以降低匹配分支复杂度。
- 参数约束优化:通过 params 或 key(pattern) 限制占位符范围(如数字主键),减少无效匹配。
- 中间件链精简:使用 withoutMiddleware 移除不必要的中间件,或使用 skipAllMiddleware 对字面路由跳过全链。
- 名称解析幂等:qualifyName 避免重复前缀叠加,降低字符串操作开销。
- 新增 导航元数据零开销:navMeta 在未使用时为 null,navFields() 返回空数组,不影响性能。
故障排查指南
- 未安装累积器:在 Route::collector() 未安装时会抛出 LogicException,确保在 include 周期内调用 useCollector。
- 未知路由名:JsRouteBuilder::url 遇到未知路由名会抛 InvalidArgumentException,检查路由名是否正确、是否已注册。
- 方法不匹配:RouteEntry::acceptsMethod 对 HEAD 做 GET 兼容,若出现方法拒绝,检查 methods 白名单与动词入口选择。
- 中间件冲突:若权限/限流/鉴权未按预期生效,确认 middleware/withoutMiddleware/permission/throttle/auth 的组合与顺序。
- 资源 show 未生成:默认不包含 show,需经 only([...,'show']) 显式开启,避免静默新增 {id} GET 路由。
- 新增 导航元数据未生效:检查是否在正确的 Builder 上调用 nav() 方法,确认 navFields() 被正确合并到 fields 中。
- 新增 路由条目为空:Request::routeEntry() 返回 null 可能是未命中声明式路由,检查路由匹配逻辑。
结论
DouPHP 的核心路由构建器通过门面与 Builder 分离、值对象承载元数据、DSL 抽象横切关注点,实现了声明式、可扩展、高性能的路由构建体系。本次更新引入的 RouteNavDsl 特性和 Request::routeEntry() 方法进一步增强了路由系统的功能:
- 导航元数据支持:通过 RouteNavDsl 特性,可以在路由构建过程中传递后台导航元数据,实现精确的导航高亮归属。
- 路由条目访问:通过 Request::routeEntry() 方法,可以在控制器和中间件中直接访问命中的路由条目对象,便于动态权限控制和审计日志记录。
- 增强的构建器体系:RouteGroupBuilder 的加入提供了更灵活的分组路由构建能力。
开发者可以借助 fluent API 快速定义简单路由、带参路由、条件路由与资源路由,并通过中间件 DSL 和导航 DSL 精细控制请求处理链和导航状态。遵循最佳实践与性能优化策略,可在复杂项目中保持清晰与稳定。
附录:示例与最佳实践
-
简单路由(GET)
- 使用 Route::get(pattern, controller, action, module) 定义静态路径。
- 适用场景:后台设置页、工具页等扁平动词 URL。
- 参考路径:Route.php:131-156
-
带参数的路由
- 使用 RouteEntryBuilder::params(['id' => '\d+']) 或 RouteResourceBuilder::key('id', '[0-9]+') 限定占位符。
- 适用场景:资源项详情、编辑页等需要 ID 的场景。
- 参考路径:RouteEntryBuilder.php:105-115、RouteResourceBuilder.php:182-199
-
条件路由(多方法/任意方法)
- 使用 Route::match(['GET','POST'], ...) 或 Route::any(...) 定义多方法或 permissive 路由。
- 注意:any() 应谨慎使用,需在评审中说明无法严格化的原因。
- 参考路径:Route.php:226-264
-
资源路由(CRUD)
- 使用 Route::resource(module, controller) 批量展开标准 CRUD 动作,配合 only/except/extras 定制。
- 注意:show 不在默认集,需显式 only([...,'show']) 开启。
- 参考路径:RouteResourceBuilder.php:29-43、RouteResourceBuilder.php:201-228
-
分组路由
- 使用 Route::group(module, controller) 定义分组路由,支持 prefix/sub/name/compositeModule 等配置。
- 适用场景:模块化控制器组织,如 admin/user、front/member 等。
- 参考路径:RouteGroupBuilder.php:101-159
-
中间件绑定
- 使用 middleware([...]) 追加、withoutMiddleware([...]) 豁免、permission/throttle/auth 参数糖覆盖。
- 使用 skipAllMiddleware() 对字面路由跳过全链。
- 参考路径:RouteMiddlewareDsl.php:46-127
-
新增 导航元数据声明
- 使用 nav(['cur' => 'user', 'group' => 'people']) 声明后台导航元数据。
- 支持 action 级映射:nav(['cur' => ['default' => 'user', 'log' => 'audit']])。
- 适用于 RouteEntryBuilder、RouteResourceBuilder、RouteGroupBuilder。
- 参考路径:RouteNavDsl.php:42-52
-
新增 路由条目访问
- 在控制器中使用 request()->routeEntry() 获取命中的路由条目。
- 在中间件中使用 request()->routeEntry() 进行动态权限判断。
- 注意:未命中声明式路由时返回 null。
- 参考路径:Request.php:1318-1321
-
路由命名与前缀
- 使用 name('api.') 叠加前缀,或 name('api.book') 全量覆盖;compositeModule() 影响 emit 的 module 字段。
- 参考路径:RouteNameResolution.php:41-78
-
前端 URL 生成
- 使用 JsRouteBuilder::url(payload, name, params, options) 生成完整 URL,支持 rewrite、语言前缀、查询参数。
- 参考路径:JsRouteBuilder.php:39-92
-
最佳实践
- 优先使用具体动词入口,减少 any()。
- 明确 params/key 的正则约束,提升匹配效率。
- 合理使用 only/except 控制资源路由集合。
- 通过 withoutMiddleware 精简中间件链,必要时使用 skipAllMiddleware。
- 使用 name 前缀与 compositeModule 保持路由名与模块名一致性。
- 新增 合理使用 nav() 声明导航元数据,提升后台用户体验。
- 新增 在需要动态权限控制的场景中使用 request()->routeEntry() 获取路由信息。