thinkphp 6+ 的 $filter 默认不生效,仅在 data()->validate()->save() 链式调用且未禁用过滤时触发;直调 save(['field'=>$val]) 会绕过过滤。

ThinkPHP 的字段过滤($filter)在 6.x 版本中**默认不生效**,不是“设了就自动跑”,必须满足特定调用链才触发;多数人配了却没效果,根本原因是调用了 save(['field' => $val]) 这种直写方式,它完全跳过 $filter。
为什么 $filter 设了但没执行?
ThinkPHP 6+ 已移除 create(),$filter 不再是“入库必经环节”。它只在显式走 data()->validate()->save() 链式调用时,且未禁用过滤选项的前提下才启用。
-
$model->save(['content' => '<script>'])</script>→ ❌ 绕过$filter,直接入库 -
$model->data(['content' => '<script>'])->validate()->save()</script>→ ✅ 触发$filter(前提是没传['validate' => false]等跳过参数) -
$model->data($data)->validate()->save(['validate' => true])是冗余写法,validate()已隐含开启 - 如果验证器里用了
scene,要确保该 scene 没把过滤规则覆盖掉(验证器的filter是临时的,不改模型原始$filter)
怎么让字段过滤真正起作用?
推荐两条路径,按场景选:
- 统一入口用
beforeWrite钩子:稳定、不依赖调用方式,适合对updated_at、content等字段做硬性处理protected static function boot() { parent::boot(); static::beforeWrite(function ($model) { $model['content'] = htmlspecialchars($model['content'] ?? ''); $model['updated_at'] = date('Y-m-d H:i:s'); }); } - 保留
$filter但强制走标准链路:所有写入都封装成data()->validate()->save(),并在控制器里校验是否漏掉$user->data($request->only(['name', 'email', 'content']))->validate()->save() - 注意:如果用了
$fillable或$guarded,$filter只对白名单内的字段生效;字段名必须和数据库列名一致(比如不能写user_name却配username)
过滤函数接收什么参数?
取决于你在 $_filter(TP3.2)或 $filter(TP6+)里怎么定义第三个参数:
- 默认(不传或
false):只传字段当前值,例如'content' => 'htmlspecialchars'→ 函数收到的是字符串'<script>xxx'</script> - 设为
true:传整个数据对象,可用于跨字段逻辑,比如'status' => ['setStatusByRole', true],函数里可读$data['role'] - 函数可以是字符串(内置函数)、数组(
['class', 'method'])、闭包;闭包最灵活,但别在闭包里 throw 异常——beforeWrite钩子里 throw 才能中断写入
容易被忽略的兼容细节
字段过滤不处理访问器(accessor)、JSON 字段、关联数据,也不影响数据库查询本身。如果你在模型里定义了 getFullnameAttr,它不会被 $filter 或 beforeWrite 拦截;同理,content 是 JSON 类型字段,htmlspecialchars 会把它变成非法 JSON。
真正要防 XSS,得在输出层用模板引擎的 {{ }} 自动转义,或者在 API 响应前统一过一遍 strip_tags() —— 过滤只是第一道关,不是银弹。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











