简介
本技术文档围绕 DouPHP 的“路由条目构建器”展开,聚焦 RouteEntryBuilder 类及其在声明式路由体系中的职责。内容涵盖:
- 如何创建单个路由条目、设置路由属性与配置选项
- 路由条目的生命周期管理(从创建到注册)
- HTTP 方法限制、参数验证、默认值等高级用法
- 与控制器方法的绑定机制与参数传递方式
- 调试与测试建议
- 面向初学者的简明示例与面向高级开发者的扩展指南
项目结构
DouPHP 的路由系统采用“构建期声明 + 运行时匹配”的分层设计:
- 构建期:通过 Route 门面提供的 fluent API 在 route/*.php 中声明路由,生成 RouteEntry 并累积到清单
- 运行期:由 PrettyRouteMatcher 等匹配器根据清单进行 URL 解析与方法分发
graph TB
A["Route 门面<br/>提供 get/post/put/patch/delete/match/any"] --> B["RouteEntryBuilder<br/>单条路由链式构建"]
B --> C["RouteCollector<br/>累积当前文件的条目"]
C --> D["RouteEntry<br/>不可变清单条目"]
E["RouteManifestBuilder<br/>收集所有来源的条目"] --> F["路由清单<br/>供匹配器消费"]
B --> D
C --> F
核心组件
- RouteEntryBuilder:单条声明式路由的链式构建器,承载 pattern → controller/action 映射、HTTP 方法白名单、路由级中间件 DSL,并在构造结束或显式 register() 时推入 RouteCollector
- Route:声明式路由的静态门面,提供 get/post/put/patch/delete/match/any/group/resource 等入口,内部统一调用 makeEntry 创建 RouteEntryBuilder
- RouteEntry:不可变的路由清单条目,包含 name、pattern、params、module、controller、action、sub、methods、中间件元数据、source 等字段
- RouteCollector:每个路由文件 include 期间的累积器,负责 push/flush 与组属性栈管理
- RouteMiddlewareDsl:被多个 builder 复用的中间件 DSL,支持追加、豁免、参数覆盖、跳过全部中间件
- RouteNameResolution:被多个 builder 复用的名称解析与 compositeModule 逻辑
- RouteManifestBuilder:批量生成路由清单,汇总 declared 风格规则与系统内置端点
架构总览
构建期流程概览:
- RouteManifestBuilder 在每个 route/*.php 执行前安装 RouteCollector
- 路由文件通过 Route::get/post/... 等 fluent API 创建 RouteEntryBuilder
- RouteEntryBuilder 在 __destruct 或 register() 时生成 RouteEntry 并 push 到 RouteCollector
- RouteManifestBuilder 最终 flush 得到 RouteEntry[] 清单,供运行期匹配器使用
sequenceDiagram
participant MB as "RouteManifestBuilder"
participant RC as "RouteCollector"
participant R as "Route 门面"
participant B as "RouteEntryBuilder"
participant E as "RouteEntry"
MB->>RC : 安装 collector
MB->>R : include 路由文件
R->>B : makeEntry(...)
B->>B : 链式设置 params/sub/name/middleware
B-->>RC : __destruct/register -> push(E)
MB->>RC : flush() 获取条目列表
详细组件分析
RouteEntryBuilder:单条路由条目构建器
- 职责:封装单条路由的 pattern、controller、action、module、name、params、sub、methods 以及中间件 DSL;在析构或显式 register() 时生成 RouteEntry 并推入 RouteCollector
- 关键能力:
- 链式设置:name(params)、sub、middleware/withoutMiddleware/skipAllMiddleware/auth/throttle/permission
- 自动注册:__destruct 确保未显式 register 时仍会推入 collector
- 名称解析:结合 RouteCollector 的组属性栈,计算最终 name 与 module(compositeModule)
- 典型调用路径:Route::get/post/... -> makeEntry -> new RouteEntryBuilder -> 链式配置 -> register
classDiagram
class RouteEntryBuilder {
-collector : RouteCollector
-pattern : string
-controller : string
-action : string
-module : string
-params : array
-sub : string|null
-methods : string[]
-registered : bool
+register() void
+__destruct() void
+name(name) self
+params(params) self
+sub(sub) self
+middleware(aliases) self
+withoutMiddleware(names) self
+skipAllMiddleware() self
+auth(mode) self
+throttle(max, window) self
+permission(node) self
}
class RouteCollector {
+push(entry) void
+flush() RouteEntry[]
+currentNamePrefix() string
+currentComposite() bool
}
class RouteEntry {
+name
+route_type
+pattern
+params
+module
+controller
+action
+sub
+methods
+middleware
+without_middleware
+middleware_params
+skip_all_middleware
+is_short_url_aware
+is_family
+source
}
RouteEntryBuilder --> RouteCollector : "push(RouteEntry)"
RouteEntryBuilder --> RouteEntry : "构造并写入"
Route:声明式路由门面
- 职责:暴露 get/post/put/patch/delete/match/any/group/resource/column/simple/page 等入口;内部统一调用 makeEntry 创建 RouteEntryBuilder
- 重要语义:
- 严格动词:get/post/put/patch/delete 分别限定 HTTP 方法
- match:显式指定 methods 白名单
- any:permissive 逃生口(空 methods),需谨慎使用
- group/resource:批量展开多条 declared 条目
- column/simple/page:按风格规则展开 meta 模板为具体条目
RouteEntry:不可变清单条目
- 职责:承载一条路由的完整字段,包括 name、pattern、params、module、controller、action、sub、methods、中间件元数据、source 等
- 关键行为:
- acceptsMethod:判断是否接受某 HTTP 方法(HEAD 按 GET 处理;空 methods = permissive)
- endNamespace:根据 controller FQCN 推导所属端(Front/Admin/Api)
- toRuleArray/toArray:用于视图兼容与诊断序列化
RouteCollector:构建期累积器
- 职责:维护当前路由文件的条目集合与组属性栈;提供 push/flush 与 sourceFor 拼装
- 关键点:
- currentNamePrefix/currentComposite:支持嵌套组的 name 前缀叠加与 compositeModule 标记
- sourceFor:生成 'declared:<rel>:<name>' 形式的 source 标识
RouteMiddlewareDsl:路由级中间件 DSL
- 职责:提供 middleware/withoutMiddleware/skipAllMiddleware/auth/throttle/permission 等链式方法,将中间件细化配置写入 RouteEntry
- 输出字段:middleware、without_middleware、middleware_params、skip_all_middleware
RouteNameResolution:名称解析与复合模块
- 职责:解析最终路由名(叠加组栈前缀与单条 name)、emit 的 module 字段(compositeModule 且 sub 非空时取复合名)
- 工具方法:actionTakesBaseName、qualifyName
依赖关系分析
- RouteEntryBuilder 依赖:
- RouteCollector:push 条目、读取组属性栈
- RouteMiddlewareDsl:中间件 DSL
- RouteNameResolution:名称解析与 compositeModule
- RouteEntry:构造不可变条目
- Route 门面依赖:
- RouteEntryBuilder:创建单条条目
- RouteResourceBuilder/RouteGroupBuilder:批量展开(与本主题相关但非核心)
- RouteManifestBuilder:
- 汇总 declared、system_reserved、风格规则条目,产出清单
graph LR
R["Route 门面"] --> B["RouteEntryBuilder"]
B --> C["RouteCollector"]
B --> M["RouteMiddlewareDsl"]
B --> N["RouteNameResolution"]
B --> E["RouteEntry"]
MB["RouteManifestBuilder"] --> C
MB --> E
性能与行为特性
- 构建期开销:每条路由条目在 __destruct 或 register() 时生成 RouteEntry 并 push,整体线性复杂度 O(n)
- 方法匹配:RouteEntry::acceptsMethod 对 HEAD 特殊处理(视为 GET),空 methods 表示 permissive
- 名称解析:qualifyName 幂等拼接前缀与基名,避免重复前缀
- 中间件 DSL:仅影响 RouteEntry 的中间件元数据,不改变匹配顺序
故障排查指南
常见问题与建议:
- 未在构建期安装 collector:调用 Route::collector() 时会抛出 LogicException,需确保在 RouteManifestBuilder include 周期内使用
- 误用 any():permissive 路由应谨慎使用,仅在确实需要多动词同 URL 时使用
- 中间件冲突:skipAllMiddleware 优先级最高,若与其他中间件配置混用需注意
- 名称前缀错误:注意 name('api.') 尾点表示前缀,无尾点表示全量覆盖;group 栈可叠加前缀
- 复合模块:compositeModule 仅在 sub 非空时生效,否则 emit 的 module 不变
结论
RouteEntryBuilder 是 DouPHP 声明式路由的核心构件之一,负责将单条路由的 pattern、控制器动作、HTTP 方法与中间件配置组装为不可变的 RouteEntry,并通过 RouteCollector 纳入路由清单。配合 Route 门面与 RouteManifestBuilder,实现了从声明到清单生成的完整链路。对于初学者,可从简单的 get/post 声明入手;对于高级开发者,可通过中间件 DSL 与名称解析机制实现细粒度的权限、限流与命名策略。
附录:使用示例与最佳实践
基础示例:声明一条 GET 路由
- 在 front/route 目录下新建或编辑路由文件,使用 Route::get 声明一条路由
- 参考示例文件:routes_js.php
高级用法:HTTP 方法限制与参数验证
- 使用 Route::match 显式指定 methods 白名单
- 使用 params 设置占位符正则约束(如 id 必须为数字)
- 使用 sub 设置子控制器段
中间件配置:权限、限流与豁免
- 使用 permission 设置权限节点
- 使用 throttle 设置限流配额
- 使用 withoutMiddleware 豁免默认栈中的中间件
- 使用 skipAllMiddleware 跳过全部中间件
名称解析与复合模块
- 使用 name('api.') 设置前缀,或 name('api.book') 全量覆盖
- 使用 compositeModule 在 sub 非空时 emit 复合 module
调试与测试
- 检查 RouteEntry::acceptsMethod 是否正确接受目标 HTTP 方法
- 检查 RouteEntry::endNamespace 是否正确推导所属端
- 使用 RouteManifestBuilder 产出的清单进行断言