自定义 filter 回调函数必须且仅接收一个参数(待过滤的原始值)并返回处理后的值;多参数、引用传参、访问 $this 或 Request 实例、调用 input() 均会失败,返回 null 或 void 会导致后续逻辑异常。

ThinkPHP 的 filter 参数到底在哪生效?
它不在中间件里,也不在控制器构造函数中——filter 是 ThinkPHP 请求对象(Request)解析参数时的**预处理钩子**,仅对 input()、param() 等方法读取的 GET/POST/PUT/JSON 数据起效,对原始 $_GET 或 $request->get() 直接取值无效。
常见错误是把过滤逻辑写在控制器里,等 input('name') 返回后再处理,结果绕过了框架的自动过滤链。正确位置在配置或请求初始化阶段:
-
config/app.php中设置全局'default_filter' => 'htmlspecialchars,trim'(仅对input()有效) - 调用
input('name', '', 'htmlspecialchars|trim')显式传入过滤器链 - 在
app/common.php或服务提供者中绑定自定义过滤回调:think\facade\Request::filter(function ($value) { return is_string($value) ? stripslashes($value) : $value; });
自定义 filter 回调函数必须满足什么签名?
ThinkPHP 5.1+ 要求回调接收且**只接收一个参数**(待过滤的原始值),返回处理后的值。多参数、引用传参、或试图在回调里访问 $this 或 Request 实例都会失败。
典型翻车场景:
立即学习“PHP免费学习笔记(深入)”;
- 写成
function ($value, $key) { ... }→ 报错Too few arguments - 在回调里调用
input('token')→ 触发无限递归(因为input()又触发 filter) - 返回
null或void→ 后续逻辑收到null,但无提示
安全写法示例:
Request::filter(function ($value) {
if (is_string($value)) {
return trim(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
}
return $value;
});
input() 和 param() 的 filter 行为为什么不一样?
input() 严格按你传的 filter 参数或配置执行;param() 则会**合并路由变量 + 请求参数 + 隐式合并规则**,导致 filter 被覆盖或跳过。
比如路由定义 user/:id,访问 /user/123?name=<script></script>:
-
input('name', '', 'htmlspecialchars')→ 正确过滤 script 标签 -
param('name', '', 'htmlspecialchars')→ 可能返回未过滤的原始值,因为param()优先从路由和合并参数中取,不强制走 filter 链 -
param('id')→ 路由参数默认不经过任何 filter,哪怕你配了default_filter
结论:敏感字段(如用户输入、HTML 内容)务必用 input() 显式指定 filter,别依赖 param()。
filter 回调里做类型转换容易踩哪些坑?
想把字符串 '123' 自动转成整型?别在全局 filter 回调里写 intval() —— 它会把 '0'、''、'abc' 全变成 0,丢失业务语义。
更隐蔽的问题:
- JSON 请求体中
{"status": "0"}→input('status', 1, 'intval')返回0,但你本意可能是布尔关闭状态 - 日期字符串
'2024-01-01'被strtotime()过滤后变成时间戳,后续再用date()格式化时易出错 - filter 回调无法区分字段来源(GET 还是 POST),同一回调对所有字段生效,灵活性差
推荐做法:过滤只做安全净化(htmlspecialchars、trim、strip_tags),类型转换放在业务逻辑层,用 input('id/d', 0) 这类内置类型后缀更可靠。
最常被忽略的一点:filter 回调在请求生命周期早期执行,此时模型验证、权限检查都还没开始。别指望它替代业务校验,也别在里面查数据库或调用远程服务——性能和事务边界全乱了。











