闭包查询能防SQL注入因where()等方法默认参数绑定,变量经PDO bindValue处理而非字符串拼接;但闭包内若用字符串拼接、exp、order()等仍危险。

闭包查询为什么能防 SQL 注入
因为闭包里的 where()、like() 等方法默认走参数绑定流程,框架会把变量值单独传给 PDO 的 bindValue(),而不是拼进 SQL 字符串里。哪怕你在闭包里写 $query->where('name', input('name')),底层生成的也是 WHERE name = ? + 绑定值,不是 WHERE name = 'admin' OR 1=1'。
闭包里哪些写法仍然危险
闭包本身不自动免疫注入,关键看里面怎么用查询方法:
-
where('name = "' . input('name') . '"')❌ —— 字符串拼接,直接退化为裸 SQL -
where(['name' => ['exp', "UPPER(name) = '" . input('name') . "'"]])❌ ——exp表示“原样执行”,跳过所有绑定 -
order(input('sort'))❌ —— 排序字段不能参数化,必须白名单校验,不能靠闭包“包住”就放心 -
where('id', 'in', input('ids'))✅ —— 框架自动处理IN绑定,前提是input('ids')是数组(如[1,2,3])
闭包配合动态条件的真实写法
常见于后台搜索、多条件筛选等场景,既要灵活又要安全:
- 用
use ($var)显式引入外部变量,避免闭包内直接读$_GET - 条件分支用
if套$query->where(),别堆在一行里拼字符串 - 模糊查询必须用
like()或['like', '%' . $kw . '%'],别写"name LIKE '%{$kw}%'" - 空值或无效参数要提前判断,比如
if (!empty($status)) { $query->where('status', $status); }
示例:
Db::name('user')->where(function ($query) use ($name, $status) {
if ($name) {
$query->where('name', 'like', '%' . $name . '%');
}
if ($status !== null) {
$query->where('status', $status);
}
})->select();
容易被忽略的兼容性细节
ThinkPHP 6 默认启用 PDO 预处理,但以下情况会让绑定失效:
立即学习“PHP免费学习笔记(深入)”;
- MySQL 连接配置中
PDO::ATTR_EMULATE_PREPARES被设为true(Docker 或低版本 MySQL 常见),此时?占位符实际还是字符串替换 - 调用
strict(true)后,空闭包(如where(function ($q) {}))会抛异常,防止条件被静默忽略 -
failException(true)配合使用,能让绑定失败或语法错误在开发期暴露,而不是上线后才被扫出
真正防住注入的,从来不是“用了闭包”,而是“闭包里没拼字符串、没用 exp、没放行不可信字段”。只要有一处松动,整条链就断了。



















