简介
本文件面向 DouPHP 前台控制器层的开发者,系统性说明 BaseController 基类的设计与能力、业务控制器的实现模式(产品、订单、用户等)、请求参数处理、服务调用、视图数据准备的标准流程,以及控制器与前端的模板数据传递方式。文档同时提供创建新控制器、处理表单提交、调用服务层方法的步骤化指引,并总结常见问题的解决方案与最佳实践。
重要更新:BaseController现在引入了pageFactVars()方法,实现了统一的页面事实和导航变量管理,自动处理导航列表注入、顶级cur变量推导和稳定路由事实注入,大幅简化了控制器的开发工作。
项目结构
前台控制器位于 front/controller 下,按模块划分子目录(如 product、order、user 等)。所有前台控制器统一继承 front/controller/BaseController.php,后者在 core/controller/BaseController.php 之上扩展了前台特有的 view()、layoutVars()、respond() 等方法,用于渲染模板、注入布局公共变量、统一成功响应分流。
graph TB
A["前端请求"] --> B["路由解析"]
B --> C["前台控制器<br/>front/controller/*"]
C --> D["BaseController<br/>view()/pageFactVars()/layoutVars()"]
C --> E["服务层<br/>Service/*"]
C --> F["模型/门面<br/>Model/Facade"]
D --> G["模板引擎<br/>TemplateRendererInterface"]
D --> H["JSON/重定向响应"]
D --> I["NavigationBuilder<br/>统一导航管理"]
I --> J["自动注入导航变量"]
核心组件
- 前台 BaseController:封装 view()、pageFactVars()、layoutVars()、respond()、buildLinkUserCenter(),负责模板渲染、页面事实变量合并、布局公共变量注入、成功响应分流(JSON 或 303 重定向)与会员中心导航构建。
- 通用 BaseController:提供 json()、response()、redirect() 等通用响应构造方法,供三端共享。
- 业务控制器:以 ProductController、OrderController、UserController、AuthController 为代表,展示标准的数据获取、校验、服务调用、视图数据组装与返回流程。
- 表单请求验证:BaseFormRequest 统一收集全部字段错误;具体场景的 FormRequest(如 LoginFormRequest、RegisterFormRequest)定义规则。
- 导航系统:NavigationBuilder 统一管理导航逻辑,通过 middle() 方法设置上下文模块,自动推导顶级 cur 变量。
架构总览
前台控制器的典型调用链如下:
- 路由将请求分发到具体控制器方法。
- 控制器通过依赖注入的服务对象完成业务逻辑。
- 控制器使用 BaseController::view() 渲染模板,并通过 pageFactVars() 自动注入页面事实变量。
- layoutVars() 提供布局公共变量,与 action 数据合并后由 pageFactVars() 统一处理。
- 对于 POST 成功,使用 BaseController::respond() 根据 Accept 头决定 JSON 或 303 重定向。
- 异常通过 DomainException 抛出,携带错误消息与跳转地址。
sequenceDiagram
participant U as "浏览器"
participant R as "路由"
participant C as "业务控制器"
participant BC as "BaseController"
participant NB as "NavigationBuilder"
participant V as "模板引擎"
participant H as "HTTP响应"
U->>R : "GET/POST /route=xxx"
R->>C : "调用控制器方法"
C->>BC : "view(模板, 数据)"
BC->>NB : "middle()设置上下文"
NB-->>BC : "contextModule()"
BC->>BC : "pageFactVars()统一处理"
BC->>V : "渲染模板"
V-->>H : "HTML响应"
C->>H : "respond(成功分流)"
H-->>U : "HTML/JSON/重定向"
详细组件分析
BaseController(前台)设计
- view(template, data, statusCode):构造 ViewResponse,并将 action 数据与 layoutVars() 合并后传入 pageFactVars() 统一处理。
- pageFactVars(merged):新增核心方法 - 页面事实与导航派生变量的统一收口,自动处理三类键:
- 三个导航列表键 fail-safe:渲染路径漏声明时用同源单例兜底构建
- 顶层
cur:由最近一次 NavigationBuilder::middle() 解析出的归属模块派生 route_module/route_action:当前页命中的稳定路由事实
- respond(request, redirectUrl, data, message):对 JSON 请求返回带 redirect_url 的成功信封;普通表单提交返回 303 重定向。
- layoutVars():返回空数组,子类可重写叠加导航、SEO、当前模块标记等公共变量。注意:不再需要手动设置 cur 变量,由 pageFactVars() 自动处理。
- buildLinkUserCenter(currentModule):当用户已登录且会员中心 Builder 注册时,返回会员中心子导航 ViewModel。
classDiagram
class CoreBaseController {
+json(data, status, options)
+response(content, status, headers)
+redirect(url, status)
}
class FrontBaseController {
+view(template, data, status)
+pageFactVars(merged) array
+respond(request, url, data, message)
+layoutVars() array
+buildLinkUserCenter(module) array
}
FrontBaseController --|> CoreBaseController : "继承"
产品控制器(ProductController)
- 列表页 index:解析分类 ID、归档信息、分页配置,调用 ProductService 构建列表数据,组装面包屑、SEO、导航、商品树与相关商品,最终渲染 product_category.dwt。
- 详情页 show:解析商品 ID,调用服务获取详情,按需加载属性、优惠券、评论等模块数据,生成结构化 SEO 数据,渲染 product.dwt。
- 更新:不再需要手动设置 cur 变量,由 pageFactVars() 自动处理。导航变量通过 NavigationBuilder 统一管理。
flowchart TD
Start(["进入列表/详情"]) --> Parse["解析路由参数<br/>分类ID/商品ID"]
Parse --> Validate{"是否有效?"}
Validate --> |否| ThrowErr["抛出领域异常<br/>page_wrong"]
Validate --> |是| LoadData["调用服务获取数据"]
LoadData --> BuildView["组装视图数据<br/>SEO/导航/面包屑/列表或详情"]
BuildView --> CallPageFactVars["BaseController::pageFactVars()<br/>自动处理导航变量"]
CallPageFactVars --> Render["渲染模板"]
Render --> End(["返回响应"])
订单控制器(OrderController)
- 作为 route=order 的薄入口,直接重定向到购物车页面,体现"入口控制器"的职责单一性。
用户中心控制器(UserController)
- 会员中心首页 index:获取用户信息,未登录则重定向至登录;组装欢迎信息与面包屑,渲染 user.dwt。
- area 接口:返回省市区 JSON,支持初始化与级联查询。
- filebox/filedel:文件上传与删除,支持草稿模式与真实主键模式,回显附件画廊。
- 更新:不再需要手动设置 cur 变量,由 pageFactVars() 自动处理。
认证控制器(AuthController)
- 注册/登录/手机验证码登录/找回密码/退出:完整的前台认证流程。
- 表单验证:通过 FormRequest 进行字段校验;结合 Honeypot、Captcha、Session 校验防刷与时效。
- 业务调用:RegistrationService、LoginService 负责账号创建、凭据校验、自动建号等。
- 成功响应:统一使用 respond() 分流 JSON 或 303 重定向。
- 更新:不再需要手动设置 cur 变量,由 pageFactVars() 自动处理。
sequenceDiagram
participant U as "用户"
participant AC as "AuthController"
participant FR as "FormRequest"
participant LS as "LoginService"
participant RS as "RegistrationService"
participant AUTH as "auth('front')"
participant RESP as "BaseController : : respond"
U->>AC : "POST 登录/注册"
AC->>FR : "validated()"
FR-->>AC : "校验通过/抛错"
alt 登录
AC->>LS : "validateLoginCredentials(...)"
LS-->>AC : "用户凭据结果"
AC->>AUTH : "login(user, remember)"
else 注册
AC->>RS : "createUser(insertData, field, sns)"
RS-->>AC : "创建结果"
AC->>AUTH : "login(user)"
end
AC->>RESP : "respond(request, redirectUrl)"
RESP-->>U : "JSON/303"
表单请求验证体系
- BaseFormRequest:统一 validated() 收集全部字段错误,便于前端一次渲染所有错误位。
- LoginFormRequest:定义用户名、密码、记住我、验证码字段规则。
- RegisterFormRequest:根据全局配置动态选择邮箱/手机号模式,校验唯一性与验证码必填条件。
flowchart TD
Enter(["进入 validated()"]) --> Rules["读取 rules()"]
Rules --> Data["读取 validationData()"]
Data --> Validator["调用 Validator.validate(..., collectAll=true)"]
Validator --> Errors{"是否有错误?"}
Errors --> |是| ThrowErr["抛出包含 errors 的异常"]
Errors --> |否| Return["返回过滤后的数据"]
导航系统重构
- NavigationBuilder:统一管理导航逻辑,通过 middle() 方法设置上下文模块,自动推导顶级 cur 变量。
- pageFactVars():作为页面事实和导航派生变量的统一入口点,自动处理导航列表注入、顶级cur变量推导和稳定路由事实注入。
- 集中化导航模式:移除了大量控制器中手动设置'cur'变量的代码,体现了新的集中化导航模式。
flowchart TD
MiddleCall["NavigationBuilder::middle()"] --> SetContext["设置 contextModule"]
SetContext --> PageFactVars["BaseController::pageFactVars()"]
PageFactVars --> CheckCur{"检查cur变量"}
CheckCur --> |不存在| GetContext["NavigationBuilder::contextModule()"]
GetContext --> AssignCur["设置 merged['cur']"]
CheckCur --> |存在| SkipCur["跳过cur设置"]
AssignCur --> RouteModule["设置route_module/action"]
SkipCur --> RouteModule
RouteModule --> ReturnMerged["返回合并后的数据"]
依赖关系分析
- 前台 BaseController 依赖核心 BaseController 提供的通用响应构造方法。
- 业务控制器通过构造函数注入服务(如 ProductService、NavigationBuilder、SeoResolver、SchemaService),遵循依赖注入原则,降低耦合。
- 表单请求依赖 Validator 与语言包,确保多语言错误提示。
- 认证流程依赖 auth('front')、Session、Captcha、Honeypot 等安全组件。
- 新增:pageFactVars() 方法依赖 NavigationBuilder 进行导航变量管理,实现了集中化的导航处理。
graph LR
BC["前台 BaseController"] --> CBC["核心 BaseController"]
PC["ProductController"] --> SVC["ProductService"]
PC --> NAV["NavigationBuilder"]
PC --> SEO["SeoResolver"]
UC["UserController"] --> PS["ProfileService"]
UC --> PRT["UserCenterPresenter"]
AC["AuthController"] --> LS["LoginService"]
AC --> RS["RegistrationService"]
AC --> AUTH["auth('front')"]
FR["BaseFormRequest"] --> VAL["Validator"]
BC --> NB["NavigationBuilder"]
NB --> CB["Context Module"]
性能考虑
- 懒求值布局变量:layoutVars() 仅在调用 view() 时执行,避免不必要的计算开销。
- 模块化加载:通过 Module::make() 按需加载可选模块(如 attribute、comment、coupon),减少无关依赖。
- 分页与配置:分页大小从配置读取,避免硬编码,便于调优。
- 缓存与索引:服务层应合理使用缓存与数据库索引(由服务实现负责),控制器侧不直接操作底层细节。
- 新增:pageFactVars() 方法采用"缺才补"策略,只在缺少相应键时才进行赋值,保证存量手写值的控制器行为不受影响。
- 新增:NavigationBuilder 使用静态缓存避免重复读表,提升导航构建性能。
故障排查指南
- 页面不存在或参数无效:控制器在解析路由参数后若发现无效,抛出 DomainException 并附带 HOME_URL 或指定路由,便于统一错误处理与跳转。
- 表单校验失败:FormRequest 会收集全部字段错误并抛出异常,前端通过 dou.form.js 渲染各字段错误位。
- 验证码/反爬:Honeypot 与 Captcha 校验失败会抛出异常,需检查 Session 与时间戳。
- 登录态重定向:已登录访问登录/注册页面会被重定向至会员中心,避免重复登录。
- 新增:如果模板中无法访问 $cur 变量,检查 NavigationBuilder::middle() 是否正确调用,确认上下文模块设置正常。
- 新增:导航高亮不正确,检查 NavigationBuilder::middle() 的参数传递,确保 currentModule、currentId、currentParentId 正确设置。
结论
DouPHP 前台控制器层通过 BaseController 抽象出通用的视图渲染、页面事实变量合并、布局变量注入与成功响应分流机制,业务控制器聚焦于参数解析、服务调用与视图数据组装。表单验证通过 FormRequest 体系集中管理,错误一次性返回以提升用户体验。整体架构清晰、职责分离明确,便于扩展与维护。
重要改进:pageFactVars() 方法的引入实现了统一的页面事实和导航变量管理,通过 NavigationBuilder 集中处理导航逻辑,大幅简化了控制器的开发工作,减少了重复代码和维护成本。新的集中化导航模式确保了导航变量的一致性和可维护性。
附录:最佳实践与常见问题
如何创建新的前台控制器
- 新建控制器类并继承 BaseController。
- 在构造函数中通过依赖注入所需服务(如 Service、导航、SEO 等)。
- 实现动作方法:解析请求参数 → 调用服务 → 组装视图数据 → 返回 $this->view(...)。
- 如需全局布局变量,重写 layoutVars() 并叠加父类返回值。
- 重要:不需要在 layoutVars() 中设置 cur、group、sub_cur 等导航变量,这些由 pageFactVars() 自动处理。
如何处理表单提交
- 使用 FormRequest 定义 rules(),并在控制器方法签名中注入。
- 调用 validated() 完成校验;失败时会自动抛出包含 errors 的异常。
- 成功后调用 respond() 统一分流 JSON 或 303 重定向。
如何调用服务层方法
- 通过构造函数注入服务实例。
- 在动作方法中调用服务方法,获取业务数据或执行变更。
- 将服务返回的数据组装进视图数据数组,交由 view() 渲染。
控制器与前端模板的数据传递
- 使用 $this->view('模板名', $data) 将数据传递给模板。
- layoutVars() 返回的数组作为默认公共变量,action 数据可覆盖同名键。
- 常用键包括 pagetitle、keywords、description、nav*、rec、code_head、ur_here、breadcrumb 等。
- 重要更新:$cur 变量现在由 pageFactVars() 自动设置,无需在控制器中手动赋值。
页面事实变量管理机制
- pageFactVars() 方法作为页面事实和导航派生变量的统一收口。
- 自动处理三类键:导航列表键 fail-safe、顶层 cur 变量、稳定路由事实。
- 采用"缺才补、有不动"策略,保证向后兼容性。
- 通过 NavigationBuilder::contextModule() 获取上下文模块,自动推导顶级 cur 变量。
flowchart TD
ActionData["Action数据"] + LayoutVars["Layout变量"] --> Merge["合并数据"]
Merge --> PageFactVars["pageFactVars()处理"]
PageFactVars --> NavCheck{"检查导航变量"}
NavCheck --> |缺失| AutoNav["自动注入导航列表"]
NavCheck --> |存在| SkipNav["跳过导航注入"]
AutoNav --> CurCheck{"检查cur变量"}
SkipNav --> CurCheck
CurCheck --> |缺失| GetContext["获取contextModule"]
CurCheck --> |存在| SkipCur["跳过cur设置"]
GetContext --> AssignCur["设置cur变量"]
SkipCur --> RouteCheck{"检查路由变量"}
AssignCur --> RouteCheck
RouteCheck --> |缺失| AssignRoute["设置route_module/action"]
RouteCheck --> |存在| SkipRoute["跳过路由设置"]
AssignRoute --> Return["返回合并数据"]
SkipRoute --> Return
常见问题与解决
- 页面报错 page_wrong:检查路由参数解析是否正确,必要时增加默认值或友好提示。
- 表单错误未显示:确认 FormRequest 的 rules() 与前端字段一致,并确保 validated() 被调用。
- 验证码失效:检查 Session 中的 verification 数据与时间戳,确保 captcha 启用且未过期。
- 登录后仍跳转到登录页:检查 auth('front')->check() 与重定向逻辑,避免循环重定向。
- 新增:模板中 $cur 变量为空:检查 NavigationBuilder::middle() 是否正确调用,确认上下文模块设置正常。
- 新增:导航高亮不正确:检查 NavigationBuilder::middle() 的参数传递,确保 currentModule、currentId、currentParentId 正确设置。
- 新增:导航列表缺失:确认 NavigationBuilder 服务已正确注入,检查导航数据配置。