where()传字符串会绕过PDO预处理,直接拼接执行;传数组或闭包才触发自动参数绑定,确保用户输入不参与SQL解析。

where() 传字符串会绕过 PDO 预处理
直接传 "id = 1 AND status = 'active'" 这类字符串给 where(),ThinkPHP 会跳过参数绑定,拼接进 SQL 原样执行。哪怕你用了 input('id'),只要没进预处理流程,SQL 注入风险就真实存在。
这不是 ThinkPHP 的 bug,是设计使然:字符串模式走的是“原生表达式解析”,不经过 PDO 的 prepare() + execute() 流程。
- 常见错误现象:
where("name = '" . input('name') . "'")看似简单,但一旦input('name')是' OR 1=1 --,就完蛋 - 使用场景:所有带用户输入的查询,尤其是搜索、列表筛选、详情页 ID 获取
- 性能影响:几乎可忽略——PDO 预处理在连接复用时有缓存,且 ThinkPHP 默认启用 prepare 模式
用数组写 where 才真正触发预处理
数组形式是 ThinkPHP 推荐的默认安全写法,底层自动转为命名占位符(:name)并调用 PDOStatement::bindValue()。
示例:
立即学习“PHP免费学习笔记(深入)”;
$name = input('name');
// ✅ 安全:生成 WHERE name = :name,自动绑定
UserModel::where(['name' => $name])->select();
<p>// ✅ 支持多条件、运算符、NULL 判断
UserModel::where([
'status' => 1,
'score' => ['>', 80],
'type' => ['in', ['vip', 'admin']],
'remark' => null, // 自动转为 IS NULL
])->find();
- 注意键名必须是字段名,不能是
'name = ?'这种表达式(那又退化成字符串模式) - 值为数组时(如
['>', 10]),ThinkPHP 内部仍拆解为绑定参数,不会拼字符串 - 如果字段名含点号(如
'user.name'),需用whereRaw()或闭包替代,数组不支持别名字段直接点语法
闭包方式适合复杂逻辑或动态条件
当 where 条件需要运行时判断、嵌套 or/and、或混用原生函数(如 FIND_IN_SET)时,闭包是最可控的选择,且仍走预处理。
示例:
立即学习“PHP免费学习笔记(深入)”;
$query = input('q');
UserModel::where(function ($where) use ($query) {
$where->like('name', $query)
->orLike('email', $query)
->orWhereRaw('FIND_IN_SET(?, roles)', [$query]); // 注意:这里第二个参数是数组,触发绑定
})->select();
-
whereRaw()本身不安全,但**第二个参数传数组时,会启用 PDO 绑定**;只传字符串则不绑定 - 闭包内调用的
like()、between()等方法,全部走绑定流程 - 避免在闭包里拼字符串:
$where->where("status = " . $status)—— 这行就破防了
哪些情况 still 不走预处理?
即使你用了数组或闭包,以下写法依然会绕过绑定,必须警惕:
- 数组的 value 是完整 SQL 片段字符串:
['status' => "1 OR id > 0"]—— ThinkPHP 当作表达式处理,不绑定 -
whereRaw()只传一个参数:whereRaw("name = '" . $name . "'")—— 和字符串 where 一样危险 - 用
exp键声明表达式:['id' => ['exp', "= 123"]]—— 显式要求跳过绑定 - 数据库配置中关闭了
'params' => [PDO::ATTR_EMULATE_PREPARES => false],但更常见的是误设为true(模拟预处理),此时某些边界 case 可能降级为拼接(虽非典型,但线上建议确认)
真正安全的底线很简单:用户输入的数据,永远不直接出现在 SQL 字符串里——无论你用数组、闭包,还是 whereRaw(),只要它最终进了绑定参数列表,就过关。



















