简介
本技术文档围绕 DouPHP 的资源路由构建器 RouteResourceBuilder,系统阐述其 RESTful 设计理念与“约定优于配置”的机制:通过声明式 API 自动生成标准 CRUD 路由集合,并以 HTTP 方法与动作的映射约束资源语义。文档同时覆盖高级配置(参数定制、响应格式控制、权限绑定)、中间件集成、错误处理、测试与调试技巧,并提供面向初学者的最佳实践与面向高级开发者的扩展指南。
项目结构
DouPHP 的路由系统在构建期以“声明式路由”为主,Route::resource() 作为高层入口,内部由 RouteResourceBuilder 将模块与控制器展开为标准 CRUD 条目;每个端(前台、后台、API)拥有独立的 route 文件夹,按模块组织声明文件。
graph TB
A["Route 门面<br/>提供 resource()/group()/单动词入口"] --> B["RouteResourceBuilder<br/>CRUD 标准集 + only/except + extras"]
B --> C["RouteEntry<br/>每条声明式路由条目"]
C --> D["RouteManifestBuilder<br/>汇总 declared 条目"]
D --> E["PrettyRouteMatcher / Resolver<br/>运行时匹配与分发"]
核心组件
- RouteResourceBuilder:资源路由 fluent API 的核心实现,负责根据 module/controller 与 only/except/extras 等配置,生成标准 CRUD 路由条目并推入当前收集器。
- Route:声明式路由静态门面,暴露 resource/group/单动词等方法,供各端 route/*.php 在构建期使用。
- RouteMiddlewareDsl:为 builder 提供 middleware/withoutMiddleware/skipAllMiddleware 以及 permission/throttle/auth 参数糖 DSL。
- RouteNameResolution:统一解析路由名前缀与 compositeModule 行为,确保 nameBase 与 emit 的 module 字段符合预期。
- RouteEntryBuilder:单条路由的 fluent 构建器,与 ResourceBuilder 共同产出 RouteEntry。
- MiddlewareRegistry:组装默认栈与路由级细化,形成最终可执行的中间件链。
架构总览
资源路由从声明到运行的关键路径如下:
sequenceDiagram
participant Dev as "开发者"
participant Route as "Route 门面"
participant RRB as "RouteResourceBuilder"
participant RC as "RouteCollector"
participant RMB as "RouteManifestBuilder"
participant PRM as "PrettyRouteMatcher/Resolver"
Dev->>Route : 调用 resource(module, controller)
Route-->>RRB : 返回 builder 实例
Dev->>RRB : 链式 prefix/sub/only/except/get/post/...
RRB->>RC : register() 时 push RouteEntry[]
Note over RRB,RC : 析构或显式 register 触发
RMB->>RC : 收集 declared 条目
RMB-->>PRM : 构建匹配规则
PRM-->>Dev : 运行时按 pattern/methods/action 分发
详细组件分析
RouteResourceBuilder 类分析
-
设计要点
- 默认标准动作集:index/create/store/edit/update/destroy(不含 show),避免对未实现 show 的资源静默新增 {id} GET 路由。
- 资源映射表:定义每个动作对应的 URL suffix、HTTP methods 白名单与是否需要 {id}。
- 只读 extras:通过 get/post/put/patch/delete/any 追加非标准动作,any 为 permissive 逃生口。
- 参数定制:key(name, pattern) 支持成员动作路径参数名与正则约束替换。
- 名称解析:name() 支持前缀或全量覆盖;actionTakesBaseName 决定 index 与根动作取裸 nameBase。
- 中间件 DSL:middleware/withoutMiddleware/skipAllMiddleware 及 permission/throttle/auth 参数糖。
- 幂等注册:register() 与 __destruct 保证仅一次展开。
-
复杂度与性能
- 展开过程为线性遍历 actions 与 extras,时间复杂度 O(n),空间复杂度 O(n)。
- 通过 only/except 过滤减少条目数量,降低 manifest 规模与运行时匹配成本。
-
错误处理与边界
- 无 collector 时抛出 LogicException(fail-fast)。
- except 会同时作用于标准集与 extras;extras 与默认集同名冲突会被跳过,避免重复。
classDiagram
class RouteResourceBuilder {
-collector : RouteCollector
-module : string
-controller : string
-prefix : string
-sub : string?
-keyName : string
-keyPattern : string
-onlyList : string[]?
-exceptList : string[]
-extraBuckets : array
-registered : bool
+__construct(collector,module,controller)
+prefix(prefix) self
+sub(sub) self
+name(name) 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
+register() void
+__destruct() void
-addExtraBucket(methods,actions) self
-pushEntry(action,pattern,methods,params,nameBase,mwFields) void
-resolveStandardActions() string[]
-resolveExtraActions() array
}
资源路由展开流程(算法流程图)
flowchart TD
Start(["进入 register"]) --> ResolveStd["解析标准动作集<br/>应用 only/except"]
ResolveStd --> StdEmpty{"是否仍有标准动作?"}
StdEmpty -- 否 --> ResolveExt["解析 extras 动作列表<br/>去重/except 过滤"]
StdEmpty -- 是 --> BuildStd["按 resourceMap 生成 pattern/methods/params"]
BuildStd --> PushStd["pushEntry 写入 RouteEntry"]
PushStd --> ResolveExt
ResolveExt --> ExtEmpty{"是否仍有 extras?"}
ExtEmpty -- 否 --> End(["结束"])
ExtEmpty -- 是 --> BuildExt["pattern = prefix/<action><br/>methods 来自桶"]
BuildExt --> PushExt["pushEntry 写入 RouteEntry"]
PushExt --> End
中间件集成与权限绑定
- 路由级中间件 DSL
- middleware([...]):在默认栈末尾追加额外中间件别名(支持 'alias:p1,p2' 参数形态)。
- withoutMiddleware([...]):从默认栈中过滤指定别名。
- skipAllMiddleware():跳过全部中间件(最高优先级)。
- 参数糖:permission(node)、throttle(max,window)、auth(mode) 分别覆盖对应中间件的参数。
- 运行时组装
- MiddlewareRegistry 将全局默认栈与 RouteEntry 携带的路由级细化组合,缺类时吞掉并跳过该中间件,不中断请求。
sequenceDiagram
participant RB as "RouteResourceBuilder"
participant MW as "RouteMiddlewareDsl"
participant RE as "RouteEntry"
participant MR as "MiddlewareRegistry"
RB->>MW : middleware/withoutMiddleware/skipAllMiddleware/参数糖
MW-->>RB : middlewareFields()
RB->>RE : pushEntry(fields + mwFields)
MR->>RE : 读取路由级细化
MR-->>MR : 组装最终中间件链
名称解析与复合模块
- name() 支持前缀或全量覆盖;qualifyName 幂等拼接,避免重复前缀。
- compositeModule() 仅在 sub 非空时生效,影响 emit 的 module 字段(用于后台权限/菜单/审计/校验),不影响路由名与 URL。
实际使用示例与模式
- 单资源:基础 CRUD 集合
- 嵌套资源:子控制器 + 自定义 prefix/sub
- 参考:Route::resource('product', CategoryController::class)->prefix('product/category')->sub('category');:35-37
- 前台栏目:column 风格(非 resource,但体现前端路由风格化)
- 参考:Route::column('article', ArticleController::class);
依赖关系分析
- RouteResourceBuilder 依赖
- RouteCollector:累积当前 route 文件的 RouteEntry。
- RouteMiddlewareDsl:中间件 DSL。
- RouteNameResolution:名称解析与 compositeModule。
- RouteEntry:最终产物。
- 与 Route 门面的关系
- Route::resource() 创建并返回 RouteResourceBuilder,完成声明式入口。
- 与 Manifest/Matcher 的关系
- RouteManifestBuilder 收集 declared 条目,PrettyRouteMatcher/Resolver 在运行时进行匹配与分发。
graph LR
Route["Route 门面"] --> RRB["RouteResourceBuilder"]
RRB --> RMW["RouteMiddlewareDsl"]
RRB --> RN["RouteNameResolution"]
RRB --> RE["RouteEntry"]
RRB --> RC["RouteCollector"]
RC --> RMB["RouteManifestBuilder"]
RMB --> PRM["PrettyRouteMatcher/Resolver"]
性能考量
- 构建期展开为线性操作,only/except 可减少条目数量,从而降低 manifest 体积与运行时匹配开销。
- any() 为 permissive 逃生口,应谨慎使用,避免破坏方法语义导致匹配歧义。
- 合理设置 key(name, pattern) 的正则约束,有助于提升路由匹配效率与安全性。
故障排查指南
- 常见问题
- show 未开启:默认不包含 show,需经 only([...,'show']) 显式开启。
- 重复 action:extras 与默认集同名冲突会被跳过,避免重复路由。
- 中间件缺失:缺类时 MiddlewareRegistry 会跳过该中间件,不会 fatal。
- 调试建议
- 检查 Route::resource 链式配置是否正确(prefix/sub/only/except/key)。
- 查看生成的 RouteEntry 字段(name/pattern/methods/module/controller/action/sub)是否符合预期。
- 确认中间件 DSL 是否被正确合并到 RouteEntry 的 middleware 字段。
结论
RouteResourceBuilder 以“约定优于配置”为核心,通过声明式 API 自动生成标准 CRUD 路由,结合 only/except/extras 与中间件 DSL,满足从简单资源到复杂场景的多变需求。配合 Route 门面与 Manifest/Matcher 体系,实现了构建期声明与运行时分发的清晰解耦。遵循本文的最佳实践与扩展指南,可在保证一致性与安全性的前提下高效构建 RESTful API。
附录
RESTful 动作与 HTTP 方法映射(资源路由)
- index → GET
- create → GET
- store → POST
- show → GET(需 only 显式开启)
- edit → GET
- update → PUT/PATCH
- destroy → DELETE
常用配置项速查
- prefix:URL 前缀(默认 module)
- sub:子控制器段(影响 nameBase 与 emit module)
- only/except:标准动作过滤
- key(name, pattern):成员动作路径参数名与约束
- middleware/withoutMiddleware/skipAllMiddleware:中间件细化
- permission/throttle/auth:参数糖覆盖默认中间件参数
初学者最佳实践
- 优先使用 resource 声明标准 CRUD,必要时用 only/except 裁剪。
- 需要额外读/写动作时使用 get/post/put/patch/delete,尽量避免 any。
- 使用 key() 明确成员 ID 的语义与约束,便于控制器直接读取具名参数。
- 通过 permission/throttle/auth 精确控制权限、限流与鉴权策略。
高级扩展指南
- 自定义资源映射:在 resourceMap 基础上扩展新的动作与路径形态。
- 扩展中间件 DSL:在 RouteMiddlewareDsl 中添加新的参数糖或过滤逻辑。
- 扩展名称解析:在 RouteNameResolution 中增加新的命名策略或复合模块行为。