ThinkPHP5回调函数必须写在全局配置、服务提供者(如app/common.php)或显式传入处;filter回调须单参数且不可调input();数据库事件需在初始化阶段注册;数组处理勿混用input()与array_map。

ThinkPHP5 的回调函数写在哪?不是控制器里
TP5 的回调函数不能随便塞进控制器方法里执行——尤其是涉及请求过滤、数据库事件、中间件链这类机制时,位置错,回调就压根不触发。真正起效的位置只有三个:全局配置、服务提供者(app/common.php 或 app/provider.php)、或者在具体调用处显式传入(如 input() 第三个参数)。写在控制器里看似“能跑”,实则绕过了框架的预处理流程,等于白写。
filter 回调必须单参数、不能调 input()
自定义请求过滤回调(比如统一做 htmlspecialchars + trim)必须严格满足签名:function ($value) { ... }。常见翻车点:
- 写成
function ($value, $key)→ 报错Too few arguments - 在回调里调用
input('token')→ 触发无限递归(因为input()本身又会触发 filter) - 返回
null或没return→ 后续逻辑拿到null,但无任何提示,排查极难 - 试图访问
$this或Request实例 → 调用失败,静默返回原始值
安全写法示例:
<pre class="brush:php;toolbar:false;">use think\facade\Request;
Request::filter(function ($value) {
if (is_string($value)) {
return trim(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
}
return $value;
});
数据库查询事件回调要注册在初始化阶段
像 before_select、<code>after_insert 这类查询事件,回调必须在模型或控制器的初始化阶段注册,否则查完才绑,根本来不及。典型错误是把 Query::event() 写在某个 action 方法里,结果只对那次请求生效,且可能因执行顺序问题漏掉。
推荐位置:app\common.php(全局生效)或控制器的 _initialize() 方法(当前控制器作用域):
<pre class="brush:php;toolbar:false;">use think\db\Query;
// 全局注册,所有 select 前都触发
Query::event('before_select', function ($option, $query) {
// $option 是查询选项数组,$query 是 Query 实例
if (isset($option['table']) && $option['table'] === 'user') {
$query->where('status', 1);
}
});
注意:$option 不保证包含全部字段,比如 <code>field 可能为空;$query 可以链式追加条件,但不能调用 select(),否则引发重复查询。
array_map / array_filter 里的回调别混用 TP5 的 input() 和 param()
你在写数组处理逻辑(比如批量清洗 POST 数据)时,如果回调里混用了 input(),会出大问题。因为 input() 是请求级入口,而 array_map 是纯 PHP 数组遍历,二者生命周期和上下文完全不同。
正确做法是:先用 input('data/a.*') 拿到原始数组,再用原生回调处理:
<pre class="brush:php;toolbar:false;">$raw = input('data/a.*', []);
$safe = array_map(function ($v) {
return is_string($v) ? htmlspecialchars(trim($v)) : $v;
}, $raw);
千万别这么写:array_map(function ($k) { return input("data/a/{$k}"); }, $keys)——这会反复触发 filter 链,还可能因 key 不存在返回 null,破坏数据结构。
最易被忽略的一点:TP5 的 param() 对路由参数默认不走 filter,哪怕你配了 default_filter。所以敏感字段永远优先用 input() 显式带过滤器,别图省事用 param() 混着来。

















