tp6+ 的 $filter 默认不自动运行,仅在 data()->validate()->save() 链式调用中生效;save(['field'=>'val']) 直通数据库,跳过 $filter、验证及事件。

模型字段设了 $filter 但入库后仍是原始 HTML?不是配置没生效,是根本没触发——TP6+ 的 $filter 默认不自动运行,只在特定链式调用中起作用。
为什么 $filter 在 save() 里完全不执行
很多人把 protected $filter = ['content' => 'htmlspecialchars'] 写进模型,就以为所有写入都会被转义。实际只有 $model->data($data)->validate()->save() 这种写法才走 $filter 流程;而 $model->save(['content' => '<script>'])</script> 是直通数据库的「快捷通道」,跳过全部模型层逻辑。
-
save(['field'=>'val'])绕过data()、validate()、$filter,甚至不触发事件 -
create()在 TP6+ 已废弃,别再找它默认过滤的影子 -
$filter只处理普通字段,对 JSON 字段、关联数组、嵌套结构完全无效 - 即使触发,
htmlspecialchars也仅做基础转义,不防onerror=alert(1)这类属性级 XSS
真正可控的入库前转义入口:before_write 事件
比依赖 $filter 更可靠的是显式绑定 before_write 事件,在数据落库前统一处理。它不挑调用方式,save()、data()->save()、批量更新全生效。
- 在模型的
initialize()中注册:$this->event('before_write', [$this, 'escapeFields']); -
escapeFields()方法里可针对性处理:$data['content'] = htmlspecialchars($data['content'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', true); - 注意第四个参数
true:防止二次转义(比如已转义过的数据又被过一遍) - 富文本字段不要硬塞
htmlspecialchars,应改用HTMLPurifier实例净化一次后存库
模板输出和 API 返回必须区分对待
入库转义 ≠ 输出安全。入库时转义了,模板里再用 {!! $data !!} 或 API 直接返回 JSON,照样执行脚本。
- Blade/ThinkTemplate 中
{$content}默认转义,但{:content}或{!! content !!}会跳过——此时必须确保入库值已是净化后的 - API 场景下,
$casts = ['content' => 'string']不能替代转义,反而可能让htmlspecialchars后的字符串被当成普通字符串序列化,出现<script>这种双重编码 - 推荐做法:入库存原始内容,输出时按上下文处理——模板用
{$content},API 用 Resource 类统一调用HTMLPurifier或白名单过滤
最易被忽略的一点:before_write 事件对批量操作(如 Db::name('table')->insertAll())也不生效,这类场景只能靠中间件或控制器层提前过滤。入库前转义不是开关,是分层动作——哪一层做、对谁做、怎么验证效果,得清清楚楚。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











