文档目录
数据验证

简介

本文件聚焦 DouPHP 后台服务层的数据验证能力,围绕声明式规则、内置验证器、自定义扩展、批量与嵌套数据校验、条件校验、错误处理与数据清洗、以及性能优化与高级特性进行系统化说明。文档以代码级实现为依据,结合控制器与服务层的实际用法,给出可操作的集成方式与排错建议。

项目结构

DouPHP 的验证体系由“请求层表单类 + 核心验证器”组成:

  • 请求层:继承 FormRequest,集中定义 rules() 与可选的 validationData(),用于字段白名单与校验入口。
  • 核心验证器:Validator 负责解析规则串、执行校验、生成错误消息并抛出异常。
  • 控制器/服务:通过 FormRequest::validated() 获取已校验且白名单过滤后的数据;或在控制器中直接调用 Request::validate() 进行轻量校验。
graph TB
Controller["控制器/服务"] --> FR["FormRequest<br/>rules()/validationData()"]
FR --> V["Validator<br/>validate()/applyRule()"]
V --> DB["数据库连接<br/>unique/exists"]
V --> Lang["语言包<br/>validator_xxx / {field}_xxx"]
Controller --> Req["Request<br/>validate()前端示例"]

核心组件

  • Validator:声明式验证器,支持必填、类型、长度、范围、唯一性、存在性、正则、上传文件、图片尺寸等大量内置规则;失败时抛出领域异常,支持收集全部错误。
  • FormRequest:表单请求基类,封装 validated() 方法,自动构造 Validator、执行校验并返回白名单数据。
  • 业务 FormRequest:各模块的表单请求类,按场景(store/update)动态拼装 rules。

架构总览

下图展示从控制器到验证器的完整调用链,以及错误消息来源与数据库交互点。

sequenceDiagram
participant C as "控制器/服务"
participant FR as "FormRequest"
participant V as "Validator"
participant DB as "数据库"
participant L as "语言包"
C->>FR : validated()
FR->>V : new Validator(DB, lang_all(), module)
FR->>V : validate(data, rules)
V->>V : splitRules()/applyRule()
alt 需要唯一性/存在性
V->>DB : table(...)->where(...)->value(...)
DB-->>V : 结果
end
V->>L : getMessage(字段键, 规则, 参数)
L-->>V : 错误模板
V-->>FR : 通过或抛出 DomainException
FR-->>C : 返回白名单数据

详细组件分析

验证器 Validator:规则与流程

  • 规则解析:splitRules 将管道分隔的规则串切分为规则列表,对 regex/not_regex 中的 | 做保护合并,避免误拆分。
  • 规则执行:applyRule 根据规则名分发到具体校验逻辑,包括类型、长度、数值范围、邮箱/域名/IP、日期格式、布尔值、唯一性、存在性、确认字段、正则、枚举、前后缀、字母数字、手机号、密码、非法字符、用户名、管理员账号、别名 slug、身份证、QQ、邮编、JSON、数组、协议接受、文件/图片/扩展名/大小/尺寸等。
  • 错误消息:getMessage 优先使用自定义消息,其次查找语言包中 validator{rule} 或 {module}{field}_{rule},最后回退到占位符提示;支持 :field/:min/:max/:values/:other/:format 等占位替换。
  • 批量错误:validate 支持 collectAll=true 聚合所有字段的第一条错误后一次性抛出。
flowchart TD
Start(["进入 validate"]) --> ForEachField["遍历 rules 中的每个字段"]
ForEachField --> Split["splitRules 切分规则串"]
Split --> ApplyLoop{"逐条规则 applyRule"}
ApplyLoop --> |通过| NextField["下一个字段"]
ApplyLoop --> |失败| BuildMsg["构建错误键与消息"]
BuildMsg --> Collect{"collectAll ?"}
Collect --> |否| Throw["抛出 DomainException"]
Collect --> |是| Aggregate["聚合错误每字段仅保留第一条"]
Aggregate --> End{"是否还有字段"}
NextField --> End
End --> |是| ApplyLoop
End --> |否| Done(["完成或抛出聚合异常"])

表单请求 FormRequest:白名单与校验入口

  • validated():读取 rules(),构造 Validator,执行 validate(),并通过 array_intersect_key 基于 rules 的键集合对白名单数据进行过滤,只返回被规则声明的字段。
  • validationData():默认取 POST 数据,子类可覆盖以合并其他来源(如路由参数)。
  • normalizeScene():归一化场景名称,便于在 rules() 中按场景分支配置。

业务规则定义与场景化

  • BrandFormRequest:按 store/update 场景动态追加 required/min_value 等规则。
  • GalleryFormRequest:根据 features.slug 开关与场景,为 slug 附加 unique 规则。
  • ApproveFormRequest:售后审核同意场景的 price 与整数校验。
  • FormFormRequest:表单主表新增/编辑场景,注入 form_id 并设置最小值。

控制器中的轻量验证

  • VerificationController:在控制器中直接使用 request()->validate(['mobile' => 'required|phone']) 进行轻量校验,适用于简单场景。

服务层集成与错误处理

  • UserService:服务方法接收 validated 数据,继续执行业务校验(如必须提供联系方式),失败抛出 DomainException;成功则持久化并记录审计日志。
  • 典型模式:控制器先通过 FormRequest 完成输入校验与白名单过滤,再将干净数据交给服务处理;服务内再做业务约束校验与副作用操作。

批量与嵌套数据验证

  • 数组数据:Validator 支持 array 规则;对于数组字段,可在 rules 中声明 array,并在业务层对数组元素进行逐项校验(例如在 rules 之外循环调用 Validator 或使用自定义规则)。
  • 嵌套对象:当前 Validator 未提供原生点号路径访问(如 user.email),建议在业务层将嵌套结构扁平化后再传入 Validator,或封装一个辅助方法递归校验。
  • 条件验证:required_if 支持当其他字段命中指定值时才必填;same/different/confirmed 支持跨字段一致性校验;in/not_in/starts_with/ends_with 支持枚举与字符串约束。

文件与图片验证

  • file/image/mimes/max_kb/dimensions:针对上传文件的完整性、扩展名、大小、尺寸与宽高比进行校验;mimes 为空时回退到配置文件允许扩展名;max_kb 为空时回退到配置上限。
  • 图片尺寸:dimensions 支持 max_width/min_width/max_height/min_height/ratio 等约束。

数据库相关规则

  • unique:支持 table,field[,excludeField[,idColumn]];排除列默认为主键 id,可从 data 中取 excludeField 的值作为排除条件。
  • exists:支持 table,field;检查是否存在匹配记录。

依赖关系分析

  • FormRequest 依赖 Validator 与 DB 门面,负责组装语言包与模块名。
  • Validator 依赖数据库连接(用于 unique/exists)、Check 工具类(用于通用格式校验)、UploadedFile(文件校验)、Config(上传配置)。
  • 业务 FormRequest 依赖各自场景与配置项(如 features.slug)。
classDiagram
class FormRequest {
+rules() array
+validated() array
#validationData() array
#normalizeScene(scene) string
}
class Validator {
+validate(data, rules, messages, collectAll) void
-applyRule(ruleName, ruleParam, field, value, data) string?
-splitRules(ruleString) array
-getMessage(msgKey, rule, field, ruleParam) string
}
class UploadedFile
class Config
class Check
FormRequest --> Validator : "创建并调用"
Validator --> UploadedFile : "文件校验"
Validator --> Config : "读取上传配置"
Validator --> Check : "格式校验"

性能与最佳实践

  • 规则缓存:Validator 本身无内置规则缓存;若同一规则集频繁使用,可在应用层缓存 rules 数组与 messages,减少重复构建。
  • 批量错误:在高并发或复杂表单下,使用 collectAll=true 一次返回所有字段错误,减少往返次数。
  • 数据库规则:unique/exists 会触发查询,尽量前置本地校验(类型、长度、格式)以减少不必要查询。
  • 文件校验:仅在必要时启用 file/image/mimes/max_kb/dimensions;大图片或多文件上传时注意 I/O 开销。
  • 正则表达式:regex/not_regex 规则需确保模式高效,避免回溯灾难;必要时拆分规则或预处理。
  • 语言包消息:集中维护 validator* 与 {module}{field}_* 消息,避免硬编码,利于国际化与统一变更。
  • 白名单过滤:始终通过 FormRequest::validated() 返回的数据进行后续处理,避免手动拼接导致的安全风险。
  • 异步验证:当前 Validator 同步执行;如需异步(如远程唯一性检查),应在业务层封装为异步任务,并在最终提交前完成校验。

故障排查指南

  • 常见错误定位:
    • 规则未生效:检查 rules() 是否正确返回键值对;确认字段名与数据源一致。
    • 唯一性冲突:核对 unique 规则的 table/field/excludeField/idColumn;确认更新时排除自身主键。
    • 文件校验失败:检查 $_FILES 结构、UploadedFile::isValid()、扩展名与大小限制。
    • 图片尺寸不符:检查 dimensions 参数与图片实际尺寸。
    • 错误消息缺失:确认语言包中存在 validator{rule} 或 {module}{field}_{rule}。
  • 调试建议:
    • 临时开启 collectAll=true 收集全部错误,快速定位问题字段。
    • 在业务层打印 rules 与 data,确认传入数据形态是否符合预期。
    • 对 regex 规则进行单元测试,确保模式正确闭合。

结论

DouPHP 的验证体系以 FormRequest 为入口、Validator 为核心,提供了丰富的内置规则与灵活的消息机制,能够覆盖大多数后台表单与 API 输入校验需求。通过场景化规则、条件校验、文件与图片校验、数据库唯一性与存在性检查,以及批量错误聚合,开发者可以安全、高效地保障数据质量。在生产环境中,建议结合规则缓存、合理的前置校验与异步策略,进一步提升性能与用户体验。

添加日期:2026-10-05