ThinkPHP 的 where() 用数组更安全,因自动参数绑定防SQL注入;input() 不自动过滤,需显式指定规则如/s或/d;saveAll()/destroy()需前置权限校验;deploy配置不影响绑定,但从库可能不支持预处理导致静默失效。

ThinkPHP 的 where() 用数组还是字符串?
用数组更安全,字符串拼接是 SQL 注入高发区。ThinkPHP 对数组形式的 where() 会自动做参数绑定,而字符串形式若混入用户输入(比如 "id = ".$_GET['id']),等于把门敞开。
- ✅ 推荐:
where(['id' => input('id')])—— 自动转为预处理参数 - ❌ 危险:
where("id = ".$_GET['id'])—— 直接拼接,'id=1 OR 1=1'就能绕过 - ⚠️ 注意:
where("id = :id", ['id' => input('id')])虽然也绑定,但手写占位符易错,且不支持复杂表达式(如LIKE拼接)
input() 函数默认不过滤,必须显式指定过滤规则
input() 只负责取值,不自动转义或过滤。很多人以为用了 input('id') 就安全了,其实它和 $_GET['id'] 在数据来源上没区别,只是加了一层键名检查。
- ❌ 错误用法:
input('username')→ 用户传username=%3Cscript%3Ealert(1)%3C/script%3E照样进库 - ✅ 安全做法:
input('username/s')(/s 表示 htmlspecialchars 过滤)、input('id/d')(/d 强制转整型) - ⚠️ 坑点:
/s不防 SQL 注入,只防 XSS;/d遇到非数字返回 0,可能掩盖业务逻辑错误(比如查不到用户却返回 ID=0 的记录)
批量更新/删除时,为什么 saveAll() 和 destroy() 仍需校验权限?
ThinkPHP 的 saveAll() 和 destroy() 默认信任传入的数据结构,不会自动限制操作范围。即使你写了 UserModel::destroy([1,2,3]),如果用户能篡改提交的 ID 数组,就可能删掉不该删的记录。
- ✅ 必须前置校验:
$ids = array_intersect($inputIds, $allowedIds),再传给destroy() - ✅ 更稳妥:用主键 + 权限字段双重约束,比如
destroy(['id' => ['in', $ids], 'status' => 1]),避免越权操作 - ⚠️ 注意:
saveAll()若传入带主键的数组,会执行 UPDATE,但不会校验该主键是否属于当前用户——除非你在模型里重写updateTime或用事件钩子拦截
数据库配置里 deploy 和 read_master 开关影响参数绑定吗?
不影响。参数绑定由 PDO 预处理机制实现,与读写分离、多库部署等配置无关。但有个隐蔽问题:当开启 deploy=1 且使用 query() 手写 SQL 时,ThinkPHP 可能将语句直接发往从库 —— 而从库通常不支持预处理(尤其 MySQL 5.6 以前),导致绑定失效、回退到字符串拼接。
- ✅ 明确场景用
execute()(写操作)或query()(读操作)前确认路由目标 - ✅ 查看日志中的实际执行 SQL,确认是否出现
WHERE id = '1 OR 1=1'这类未绑定痕迹 - ⚠️ 容易被忽略:分布式部署下,开发环境单库正常,上线后因从库限制导致绑定“静默失效”
事情说清了就结束。真正卡住人的,往往不是会不会用 where(),而是忘了 input() 不过滤、destroy() 不鉴权、以及从库不支持预处理这三件事同时发生时的连锁反应。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











