Laravel FormRequest 中不应在 rules() 里调用 filter_var(),因其仅定义规则、不执行函数,且提前过滤会破坏验证上下文、导致字段值与校验结果不一致;净化应放在 passedValidation() 钩子或自定义 Rule 中,避免污染原始请求和错误定位。

直接在 Laravel 的 FormRequest 里混用 PHP 原生 filter_var 不仅多余,还容易破坏验证上下文、丢失错误定位能力——Laravel 自己的验证机制已覆盖绝大多数场景,filter_var 只应在特定字段做补充净化,且必须避开自动类型转换干扰。
为什么不要在 rules() 里调用 filter_var()
Laravel 的 rules() 方法返回的是规则字符串或数组,不是执行环境。你写 filter_var($this->email, FILTER_VALIDATE_EMAIL) 是无效的:它不会被调用,也不会报错,只是静默失效。更危险的是,有人误把过滤逻辑塞进 messages() 或构造函数里,导致验证前就修改了原始请求数据,后续规则校验对象错乱。
-
rules()只接受规则定义,不执行函数调用 - 手动提前调用
filter_var()会污染$this->validated()返回值,让字段值和验证结果不一致 - 比如
FILTER_SANITIZE_EMAIL把"test@domain.com "变成"test@domain.com",但required|email规则仍按原始带空格的值校验,出错时提示位置和实际值对不上
需要净化时,用 after validation hook 或自定义 rule
净化动作应发生在验证通过之后、数据落地之前。Laravel 提供 withValidator() 和 passedValidation() 两个钩子,后者更安全——只在全部规则通过后触发。
- 在
passedValidation()中对特定字段做filter_var($this->email, FILTER_SANITIZE_EMAIL),再手动更新$this->merge() - 或封装为自定义 rule:用
FILTER_CALLBACK+ 匿名函数,在验证流程中同步完成校验与净化,例如:'email' => ['required', new SanitizeEmailRule] - 避免对数组字段(如
tags.*)直接套用FILTER_SANITIZE_STRING,它不递归,需用array_map()手动展开
全局参数清洗别碰 $_GET/$_POST,改用中间件劫持 request 对象
有人想在 index.php 开头遍历并修改 $_GET,PHP 8.1+ 会警告“Cannot modify superglobal”,而且 Laravel 启动后早已缓存了原始输入。正确做法是写中间件,在 Request 实例上做不可逆替换。
立即学习“PHP免费学习笔记(深入)”;
- 在中间件
handle()中调用$request->all()获取当前参数,用filter_input_array(INPUT_GET, $rules)批量清洗,再用$request->replace($cleaned)替换整个参数集 - 注意跳过文件上传字段(
$request->hasFile()),filter_input_array无法处理$_FILES - 若路由含 JSON 请求体,
$request->json()->all()需单独处理,不能指望 GET/POST 规则覆盖
FILTER_SANITIZE_FULL_SPECIAL_CHARS 是 XSS 防御底线,但别滥用
这个过滤器等价于 htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),适合输出前或存入富文本字段前使用。但它会把所有引号、尖括号转义,导致手机号 138-1234-5678 变成 138-1234-5678 ——除非你明确要 HTML 上下文安全,否则别无脑套用。
- 邮箱、URL、纯数字字段优先用
FILTER_VALIDATE_*+ 单独trim(),不走FILTER_SANITIZE_* - 用户昵称、文章标题等允许部分 HTML 的字段,用
strip_tags()+ 白名单正则(如只留<b><i>)比全转义更合理 -
FILTER_SANITIZE_STRING已在 PHP 8.1 废弃,代码里出现就是隐患
真正难的不是调哪个函数,而是判断「这个字段接下来要去哪」:存数据库?拼 SQL?渲染到页面?发邮件?每个出口决定净化粒度和时机,漏掉任意一环都可能让前面的努力白费。



















