简介
本文件面向 DouPHP 框架的数据验证与转换系统,聚焦以下目标:
- 字段类型转换机制:CastResolver 的实现原理、内置转换类型与写入方向处理。
- 表单验证流程:从请求对象到验证器的完整链路,包括规则解析、错误消息优先级、上传文件校验等。
- 数据清洗与格式化:在请求层与服务层的清洗策略,以及业务规则验证的落地方式。
- 新增:控制器层中间变量验证模式的最佳实践,展示临时变量的使用模式和正则验证策略。
- 实用示例:规则定义、自定义验证器扩展、错误消息处理。
- 业务场景建议与性能优化:如何设计合理的验证策略并兼顾性能。
项目结构
围绕"验证与转换"的核心代码主要分布在如下位置:
- 验证器:core/web/validation/Validator.php(声明式规则引擎)
- 类型转换器:core/orm/casts/CastResolver.php(ORM 读取/写入时的字段类型转换)
- 通用校验工具:core/support/Check.php(正则与格式校验工具集)
- ORM 集成点:core/orm/Model.php(调用 CastResolver 进行 set/get 转换)
- 请求层示例:各模块下的 FormRequest 子类(如 admin/request/*),用于集中定义 rules() 与 validationData()
- 服务层清洗:例如 admin/service/setting/SettingService.php 中的领域清洗方法
- 新增:控制器层中间变量验证模式:多个控制器中展示的临时变量提取、类型转换和正则验证最佳实践
graph TB
A["控制器/路由"] --> B["FormRequest<br/>rules()/validationData()"]
B --> C["Validator::validate()<br/>规则解析与执行"]
C --> D["Check 工具类<br/>格式校验"]
C --> E["数据库唯一性/存在性检查"]
C --> F["上传文件校验<br/>file/image/mimes/max_kb/dimensions"]
B --> G["业务服务层<br/>数据清洗/格式化"]
G --> H["ORM Model<br/>setAttribute/getAttribute"]
H --> I["CastResolver<br/>类型转换(读/写)"]
A --> J["控制器中间变量验证<br/>shippingRaw/numberRaw/slugRaw"]
J --> K["正则表达式验证<br/>preg_match 安全过滤"]
图表来源
- core/web/validation/Validator.php:127-176
- core/orm/Model.php:707-729
- core/orm/casts/CastResolver.php:59-174
- front/controller/order/CheckoutController.php:155-156
章节来源
- core/web/validation/Validator.php:127-176
- core/orm/Model.php:707-729
- core/orm/casts/CastResolver.php:59-174
核心组件
- Validator(声明式验证器)
- 负责按规则字符串对请求数据进行校验,失败时抛出领域异常;支持收集全部错误或首错即抛。
- 内置丰富规则:required、numeric、integer、float、price、email、url、domain、ip、date/date_format、boolean、max/min/between、max_value/min_value/between_value、unique/exists、confirmed、regex/in/not_in、starts_with/ends_with、alpha/alpha_num、phone/password/illegal_char/username/admin_account/slug/idcard/qq/postcode/json/array/accepted/file/image/mimes/max_kb/dimensions。
- 错误消息优先级:自定义消息 > 语言包消息 > 占位符提示。
- CastResolver(字段类型转换器)
- 读取方向:将原始值转换为模板/业务就绪形态(int/float/bool/string/json/array、datetime/date/timestamp、attachment/attachment_thumb、defined_pairs、data_lang 等)。
- 写入方向:仅对有明确逆变换的类型进行反向转换(set_int/set_float/set_bool/set_string/set_json/set_datetime),其他展示型 cast 原样返回。
- Check(通用校验工具)
- 提供无状态静态方法,覆盖数字、邮箱、手机号、价格、域名、URL、用户名、密码、非法字符等常用校验。
- Model(ORM 集成)
- 在 setAttribute/getAttribute 中调用 CastResolver 完成写入/读取方向的类型转换。
- 新增:控制器中间变量验证模式
- 通过临时变量(如 shippingRaw、numberRaw、slugRaw)进行输入提取、类型转换和正则验证。
- 使用 preg_match 进行严格的安全过滤,确保输入符合预期格式。
章节来源
- core/web/validation/Validator.php:27-91
- core/orm/casts/CastResolver.php:25-33
- core/support/Check.php:21-27
- core/orm/Model.php:707-729
架构总览
下图展示了从请求到持久化的完整链路,包含验证、清洗、转换与存储的关键节点,以及新增的控制器中间变量验证模式。
sequenceDiagram
participant Client as "客户端"
participant Controller as "控制器"
participant FormReq as "FormRequest<br/>rules()/validationData()"
participant Validator as "Validator : : validate()"
participant Service as "业务服务"
participant Model as "ORM Model"
participant Cast as "CastResolver"
participant DB as "数据库"
Client->>Controller : "HTTP 请求"
alt 使用 FormRequest
Controller->>FormReq : "注入并调用 validated()"
FormReq->>FormReq : "组装校验数据(validationData)"
FormReq->>Validator : "传入 rules() 与数据"
Validator->>Validator : "splitRules/applyRule"
Validator-->>FormReq : "返回已过滤数据"
FormReq-->>Controller : "validated() 结果"
else 使用中间变量模式
Controller->>Controller : "提取临时变量 (xxxRaw)"
Controller->>Controller : "正则验证 (preg_match)"
Controller->>Controller : "生成安全变量 (xxx)"
end
Controller->>Service : "执行业务逻辑"
Service->>Model : "设置属性/保存"
Model->>Cast : "applySet(写入方向)"
Cast-->>Model : "转换后的值"
Model->>DB : "持久化"
图表来源
- core/web/validation/Validator.php:127-176
- core/orm/Model.php:707-729
- core/orm/casts/CastResolver.php:151-174
- front/controller/order/CheckoutController.php:155-156
详细组件分析
字段类型转换机制(CastResolver)
- 读取方向(getAttribute)
- 基础类型:int/float/bool/string/json/array
- 时间类型:datetime[:fmt]、date[:fmt]、timestamp(兼容 int 时间戳与 DATETIME 字符串双态)
- 附件:attachment / attachment_thumb(生成 URL)
- 结构化数据:defined_pairs、datalang:prefix(多语言键前缀)
- 自定义扩展:register(name, callback) 可注册任意转换逻辑
- 写入方向(setAttribute)
- 仅对具备明确逆变换的类型进行转换:set_int/set_float/set_bool/set_string/set_json/set_datetime
- 展示型 cast(attachment/attachment_thumb/defined_pairs/data_lang/timestamp)直接返回原值,避免二次转换
- 关键实现要点
- splitCastType:拆分类型与参数(如 datetime:Y-m-d)
- toTimestamp:统一转为 Unix 时间戳,兼容空值与无效值
- applySet:写入方向安全转换,避免对已是存储态的值重复转换
flowchart TD
Start(["进入 applySet"]) --> Parse["解析类型与参数"]
Parse --> Type{"类型分支"}
Type --> |int/float/bool/string/json/datetime| Convert["调用对应 set_* 转换"]
Type --> |其他| ReturnOrig["原值返回(展示型)"]
Convert --> End(["返回转换后值"])
ReturnOrig --> End
图表来源
- core/orm/casts/CastResolver.php:151-174
- core/orm/casts/CastResolver.php:176-190
- core/orm/casts/CastResolver.php:192-211
章节来源
- core/orm/casts/CastResolver.php:59-138
- core/orm/casts/CastResolver.php:151-174
- core/orm/casts/CastResolver.php:176-211
表单验证流程(Validator)
- 入口:Validator::validate(data, rules, messages, collectAll)
- 遍历每个字段的规则串,按 | 分割为规则列表
- 若规则涉及文件,则先 resolveUploadedFile 解析上传对象
- 逐条应用规则(applyRule),失败时根据优先级选择错误消息
- 支持首错即抛或收集全部错误后抛出
- 规则解析与执行
- splitRules:智能分割规则串,保护 regex:/.../ 内部 | 不被误切分
- applyRule:switch 分支处理各类规则,必要时调用 Check 工具或数据库查询
- 上传文件校验
- file/image/mimes/max_kb/dimensions:基于 UploadedFile 与配置进行合法性校验
- dimensions:解析 max_width/min_width/max_height/min_height/ratio 等约束
- 错误消息优先级
- 自定义消息(field.rule)> 语言包消息({field}{rule} 或 validator{rule})> 开发占位符
flowchart TD
VStart(["validate 入口"]) --> ForEachField["遍历字段与规则串"]
ForEachField --> Split["splitRules 分割规则"]
Split --> FileCheck{"是否含文件规则?"}
FileCheck --> |是| ResolveFile["resolveUploadedFile"]
FileCheck --> |否| ApplyLoop["逐条应用规则"]
ResolveFile --> ApplyLoop
ApplyLoop --> RuleApply{"applyRule 结果"}
RuleApply --> |通过| NextRule["下一条规则"]
RuleApply --> |失败| Message["选择错误消息(自定义>语言包>占位符)"]
Message --> Collect{"collectAll ?"}
Collect --> |否| Throw["抛出领域异常"]
Collect --> |是| Aggregate["聚合错误并继续"]
Aggregate --> NextRule
NextRule --> Done{"所有字段完成?"}
Done --> |否| ForEachField
Done --> |是| End(["结束"])
图表来源
- core/web/validation/Validator.php:127-176
- core/web/validation/Validator.php:576-641
- core/web/validation/Validator.php:683-703
章节来源
- core/web/validation/Validator.php:127-176
- core/web/validation/Validator.php:188-565
- core/web/validation/Validator.php:576-641
- core/web/validation/Validator.php:683-703
控制器层中间变量验证模式(新增)
更新 这是新增的验证模式,展示了在控制器层使用临时变量进行输入提取、类型转换和正则验证的最佳实践。
模式特点
- 临时变量命名规范:使用
xxxRaw表示原始输入,xxx表示经过验证的安全变量 - 双重安全保障:先类型转换
(string),再正则验证preg_match - 严格过滤策略:不匹配正则表达式的输入被替换为空字符串
- 常见应用场景:订单ID、文件编号、URL slug 等需要严格格式的业务标识符
典型实现示例
订单结算中的配送ID验证:
$shippingRaw = (string) $request->input('shipping_id', '');
$shippingId = preg_match('/^[A-Za-z0-9_-]+$/', $shippingRaw) ? $shippingRaw : '';
用户文件删除中的文件编号验证:
$numberRaw = (string) $request->input('number', '');
$number = preg_match('/^[a-z0-9.]+$/', $numberRaw) ? $numberRaw : '';
订单结算中的唯一标识符验证:
$slugRaw = (string) $request->input('slug', '');
$uniqueId = preg_match('/^[A-Za-z0-9_-]+$/', $slugRaw) ? $slugRaw : '';
验证流程图
flowchart TD
Input["HTTP 请求输入"] --> Extract["提取原始值 xxxRaw"]
Extract --> TypeCast["类型转换 (string)"]
TypeCast --> RegexCheck{"preg_match 正则验证"}
RegexCheck --> |匹配成功| SafeVar["生成安全变量 xxx"]
RegexCheck --> |匹配失败| EmptyString["设置为空字符串"]
SafeVar --> UseInBusiness["用于业务逻辑"]
EmptyString --> UseInBusiness
UseInBusiness --> Database["数据库操作"]
图表来源
- front/controller/order/CheckoutController.php:155-156
- api/controller/user/UserController.php:688-689
- admin/controller/file/FileController.php:140-141
适用场景
- 业务标识符验证:订单号、产品ID、分类ID等需要特定格式的标识符
- 安全敏感字段:文件名、路径、URL 参数等可能包含恶意字符的字段
- API 接口参数:对外暴露的 API 接口需要严格的输入验证
- 批量操作参数:批量删除、批量更新等操作中的 ID 列表
章节来源
- front/controller/order/CheckoutController.php:155-156
- front/controller/order/CheckoutController.php:201-202
- api/controller/user/UserController.php:688-689
- admin/controller/file/FileController.php:140-141
- admin/controller/file/FileController.php:202-203
数据清洗与格式化(请求层与服务层)
- 请求层(FormRequest 子类)
- validationData:在父类 POST 数据基础上按场景补充字段(如 update 时合并 id)
- rules:集中定义字段白名单与校验规则,同时作为 validated() 的过滤依据
- 示例:下载项、文章、用户、问答等表单请求均遵循此模式
- 服务层清洗
- SettingService.normalizeDomainValue:站点网址字段清洗,去除非法字符并确保尾部斜杠
- 其他服务可按需封装清洗方法,确保入库前数据符合业务规范
sequenceDiagram
participant Req as "FormRequest"
participant Svc as "业务服务"
participant Model as "ORM Model"
participant Cast as "CastResolver"
Req->>Req : "validationData() 组装数据"
Req->>Req : "rules() 定义规则"
Req->>Svc : "validated() 返回已过滤数据"
Svc->>Svc : "normalize* 清洗字段"
Svc->>Model : "setAttribute(...)"
Model->>Cast : "applySet(写入方向)"
Cast-->>Model : "转换后的值"
Model-->>Svc : "保存成功"
图表来源
- admin/request/download/DownloadFormRequest.php:45-85
- admin/request/chat/QuestionFormRequest.php:32-51
- admin/service/setting/SettingService.php:124-152
- core/orm/Model.php:707-729
- core/orm/casts/CastResolver.php:151-174
章节来源
- admin/request/download/DownloadFormRequest.php:45-85
- admin/request/chat/QuestionFormRequest.php:32-51
- _'/module/article/admin/request/article/ArticleFormRequest.php:24-41
- admin/service/setting/SettingService.php:124-152
具体验证规则与示例
- 规则定义
- 必填:required
- 数值范围:min_value、max_value、between_value
- 长度限制:min、max、between
- 格式校验:email、url、domain、ip、date/date_format、boolean、json、array
- 业务规则:phone、password、illegal_char、username、admin_account、slug、idcard、qq、postcode
- 唯一性与存在性:unique:table,field[,excludeField]、exists:table,field
- 上传文件:file、image、mimes、max_kb、dimensions
- 自定义验证器扩展
- 使用 CastResolver::register(name, callback) 注册自定义转换逻辑
- 在 Validator::applyRule 的 switch 中新增分支,复用 Check 工具或数据库查询
- 错误消息处理
- 优先使用 messages['field.rule'] 自定义消息
- 其次使用语言包 {field}{rule} 或 validator{rule}
- 开发阶段可使用占位符提示缺失消息
章节来源
- core/web/validation/Validator.php:27-91
- core/web/validation/Validator.php:188-565
- core/orm/casts/CastResolver.php:39-49
依赖关系分析
- Validator 依赖
- Check:格式校验工具(email、mobile、price、username 等)
- Connection:数据库连接(用于 unique/exists 规则)
- UploadedFile:上传文件对象(file/image/mimes/max_kb/dimensions)
- Model 依赖
- CastResolver:写入/读取方向类型转换
- 请求层依赖
- FormRequest:抽象出 rules()/validationData() 的统一接口
- 各模块 FormRequest 子类:按场景定制数据组装与规则
- 新增:控制器中间变量验证模式依赖
- Request:获取原始输入数据
- preg_match:正则表达式验证
- 业务常量:定义允许的字符集和格式规则
graph LR
Validator["Validator"] --> Check["Check 工具"]
Validator --> DB["Connection 数据库"]
Validator --> UF["UploadedFile"]
Model["Model"] --> Cast["CastResolver"]
FormReq["FormRequest 子类"] --> Validator
FormReq --> Service["业务服务"]
Controller["控制器"] --> Intermediate["中间变量验证模式"]
Intermediate --> Regex["preg_match 正则验证"]
图表来源
- core/web/validation/Validator.php:17-21
- core/support/Check.php:21-27
- core/orm/Model.php:707-729
- core/orm/casts/CastResolver.php:59-174
- front/controller/order/CheckoutController.php:155-156
章节来源
- core/web/validation/Validator.php:17-21
- core/support/Check.php:21-27
- core/orm/Model.php:707-729
- core/orm/casts/CastResolver.php:59-174
性能考虑
- 规则解析优化
- splitRules 在无 regex 时走 explode 快路径;含 regex 时通过平衡分隔符避免误切分,减少额外开销
- 文件校验优化
- 仅在规则列表包含文件相关规则时才解析上传文件,避免不必要的 $_FILES 访问
- 数据库规则
- unique/exists 规则按需查询,建议在高频场景下结合缓存或批量校验降低数据库压力
- 时间转换
- toTimestamp 兼容 int 与字符串,避免多次解析;写入方向统一产出 DATETIME 格式,减少后续格式化成本
- 错误消息
- 优先匹配自定义消息,减少语言包查找开销;collectAll 模式下聚合错误,减少多次异常抛出
- 新增:中间变量验证模式性能优势
- 轻量级正则验证:preg_match 比复杂业务逻辑更快
- 早期拦截:在控制器层快速拒绝非法输入,减少后续处理开销
- 内存友好:临时变量生命周期短,及时释放内存
故障排查指南
- 常见验证失败
- required:确认字段是否存在且非空;对于数组字段,empty 视为缺失
- email/url/domain/ip:检查输入格式是否符合正则或 filter_var 判定
- date/date_format:确认字符串能被 strtotime 解析或与指定格式一致
- json/array:确认值为字符串且 json_decode 合法;数组类型需为 array
- file/image/mimes/max_kb/dimensions:确认 UploadedFile 有效、扩展名命中、大小与尺寸满足约束
- 错误消息定位
- 优先检查 messages['field.rule'] 是否覆盖
- 其次检查语言包 {field}{rule} 或 validator{rule} 是否定义
- 开发阶段关注占位符提示,快速定位缺失消息
- 上传文件问题
- 确认 resolveUploadedFile 能正确解析 $_FILES
- 检查 mimes 列表与默认允许扩展名配置
- dimensions 规则需确保图片可读且宽高比计算合理
- 新增:中间变量验证模式问题排查
- 正则表达式不匹配:检查 preg_match 的模式是否正确
- 空字符串问题:确认原始输入是否为空或不符合格式
- 业务逻辑异常:检查安全变量是否正确传递给下游逻辑
章节来源
- core/web/validation/Validator.php:127-176
- core/web/validation/Validator.php:576-641
- core/web/validation/Validator.php:683-703
结论
DouPHP 的数据验证与转换系统以 Validator 为核心,结合 Check 工具与 CastResolver,形成从请求到持久化的完整闭环。新增的控制器中间变量验证模式进一步增强了系统的灵活性和安全性,提供了更细粒度的输入控制能力。通过声明式规则、丰富的内置类型转换、灵活的扩展机制以及中间变量验证模式,既能满足复杂业务场景,又具备良好的可维护性与性能表现。建议在业务设计中:
- 将规则集中在 FormRequest 的 rules() 中,保持单一职责
- 在服务层进行领域级清洗与格式化,确保入库数据一致性
- 合理使用 CastResolver 的读写方向转换,避免重复转换与数据失真
- 采用中间变量验证模式处理敏感业务标识符,确保输入安全
- 针对高频场景优化数据库规则与错误消息查找,提升整体性能
附录
- 推荐实践
- 使用 validated() 获取已过滤数据,避免在控制器中重复校验
- 对敏感字段(如 domain、password)在服务层进行额外清洗与校验
- 利用 CastResolver 的 data_lang 与 defined_pairs 简化多语言与结构化数据处理
- 采用中间变量验证模式处理业务标识符,确保输入格式安全
- 参考示例路径
- 表单请求规则定义:admin/request/download/DownloadFormRequest.php:45-85
- 场景化数据组装:admin/request/chat/QuestionFormRequest.php:32-51
- 服务层字段清洗:admin/service/setting/SettingService.php:124-152
- 中间变量验证示例:front/controller/order/CheckoutController.php:155-156
- 文件编号验证示例:api/controller/user/UserController.php:688-689
- 文件裁剪验证示例:admin/controller/file/FileController.php:140-141