thinkphp数据清洗需分层控制:请求层用中间件预处理参数,验证层按需触发filter规则,模型层通过访问器或钩子精准净化,兜底层用数据库中间件拦截直连操作。

ThinkPHP 做数据清洗,核心不是“设个配置就自动干净”,而是分层控制:请求入口、验证环节、模型写入、全局兜底。TP6+ 已移除旧版 default_filter,也不能靠模型 $filter 一劳永逸。真正管用的方式,得按数据流动路径逐段加固。
请求层:统一清洗 GET/POST 原始参数
别等数据进控制器才动手。最稳妥的做法是写一个全局中间件,在路由匹配前就处理原始输入:
- 创建
app/middleware/GlobalInputFilter.php - 在
handle()中遍历$request->get()和$request->post(),对字符串值执行trim()和htmlspecialchars()(注意避开非展示字段,如 ID、状态码) - 用
$request->withParam()把清洗后的数据重新注入请求对象,后续input()或param()拿到的就是干净值 - 不建议改
$_GET/$_POST—— ThinkPHP 的param()是封装快照,直接改超全局变量无效
验证层:按需启用 filter 规则,但别依赖它自动改原数据
验证器里的 filter 不是开关,而是手动触发的清洗指令:
- 规则中写
'content|内容' => 'require|filter:htmlspecialchars'仅在调用(new MyValidate())->check($data)时生效 -
check()只清洗验证器内部副本,$data原数组不变,你仍需用验证器返回的清洗后数据 - 嵌套字段如
user.name不支持input('user.name', '', 'htmlspecialchars'),得在中间件或模型里处理 - 高频接口慎用
htmlspecialchars全量过滤,只对可能输出到 HTML 的字段做
模型层:精准控制字段入库前净化
模型 $filter 属性在 TP6 中几乎形同虚设——它只在 data()->validate()->save() 链式调用且显式启用时才跑,save(['field'=>'val']) 直接绕过。真要可靠,用这些方式:
- 字段级清洗:定义
setUsernameAttr($value)方法,里面做trim()、正则去零宽字符、全角转半角等 - 批量注册(TP6.1+):在模型构造中调用
$this->filter(['title','desc'],'trim'),框架会自动绑定到对应setXxxAttr - 全局钩子(兼容旧版):在模型
boot()中监听before_write事件,用array_walk_recursive()扫描所有字符串字段并清洗 - 注意:JSON 字段、关联数据、
Db::table()->insert()都不走模型过滤,得另想办法
兜底层:数据库中间件拦截直连操作
如果项目大量使用 Db::table(),模型层覆盖不到,就得在数据库中间件里补漏:
- 监听
think\db\Connection的before_execute事件 - 检查 SQL 类型是
insert或update时,提取$data参数 - 对字符串字段调用
trim()或htmlspecialchars(),跳过BLOB、加密字段等二进制类型 - 前端渲染时仍需按上下文二次转义——
htmlspecialchars()后的数据不能直接塞进<script></script>或属性值里
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











