ThinkPHP的where()等查询方法默认防SQL注入,前提是使用变量传参而非字符串拼接;手动拼接SQL(如where("username = '" . $_GET['name'] . "'"))必然导致注入风险。

ThinkPHP的where()方法默认就防SQL注入,但前提是别手动拼接SQL
很多人以为要额外写预处理代码才安全,其实ThinkPHP从5.0开始,所有查询构造器(比如where()、order()、limit())底层都走PDO预处理——只要你传的是变量,不是字符串拼接,就自动绑定参数。
常见错误现象:Db::table('user')->where("username = '" . $_GET['name'] . "'")->find() 这种写法,无论加不加htmlspecialchars都拦不住SQL注入,因为单引号已被绕过。
- 正确做法:用数组或闭包传参,例如
where(['username' => $name])或where('username', $name) - 如果必须动态字段名或操作符,用
whereRaw()+ 显式绑定,例如whereRaw('status = ? AND create_time > ?', [$status, $time]) - 绝对不要用
whereRaw("username = '{$_GET['name']}'")这类字符串插值
手写原生SQL时,query()和execute()必须用问号占位符+参数数组
当你绕过查询构造器、直接调用query()(查)或execute()(增删改),ThinkPHP不会自动解析SQL里的变量,全靠你手动绑定。这时候漏掉参数数组,等于裸奔。
使用场景:复杂联合查询、存储过程调用、需要精确控制SQL结构的情况。
- ✅ 安全写法:
Db::query('SELECT * FROM user WHERE id > ? AND status = ?', [10, 'active']) - ❌ 危险写法:
Db::query("SELECT * FROM user WHERE id > {$_GET['id']}")—— 即使$_GET['id']是数字,也可能被注入10 UNION SELECT ... - 注意:
?占位符不支持绑定表名、字段名、排序方向(ASC/DESC),这些必须白名单校验后硬编码
select()、find()等链式方法本身不触发SQL,真正执行在get()或find()末尾
这个细节影响调试和错误定位。你以为where()一调就发请求了?其实没有。整个链式调用只是构建一个Query对象,直到最后调用select()、find()、value()等才会真正执行并绑定参数。
性能影响:多次链式调用不产生额外开销,但如果你在循环里反复构建相同查询,建议复用Query实例或提取为方法。
- 容易踩的坑:在
where()之后用dump($query)看到的SQL是未绑定参数的模板,比如WHERE id = ?,这不是bug,是正常行为 - 想看最终执行的SQL和参数,开启调试模式后查日志,或用
Db::getLastSql()+Db::getRealSql()(需5.1.30+) - 闭包查询中嵌套
where()也遵循同一套绑定逻辑,无需额外处理
自定义模型里重写baseQuery()或用scope时,仍要遵守参数绑定规则
很多人在模型里封装通用条件(比如软删除、租户隔离),结果在scope里又拼SQL,把前面的安全机制全废了。
兼容性影响:6.0+ 的BaseModel对scope参数传递更严格,传字符串而非数组会直接报错,反而帮你提前发现问题。
- ✅ 正确示例:
public function scopeTenant($query, $tenantId) { return $query->where('tenant_id', $tenantId); } - ❌ 错误示例:
public function scopeActive($query) { return $query->where("status = 'active'"); }—— 字符串值没走绑定,且硬编码了值 - 如果scope需要动态字段,先校验字段名是否在白名单内,再用
where($field, $value),别用whereRaw兜底
ThinkPHP的防护能力很实在,但只对“按它规矩来”的代码生效。最常被忽略的,是那些看起来无害的字符串拼接——尤其在日志埋点、动态字段、分表路由这些边缘场景里,人总想图省事手拼一句SQL,结果就把预处理通道堵死了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











