最安全的方式是查询时显式指定字段,如usermodel::field('id,name,email,created_at')->select();$hidden仅对模型序列化生效,原生查询或db类操作完全无效,白名单field()比黑名单withoutfield()更可靠。

ThinkPHP 里用 SELECT * 是最危险的默认行为之一——它会把数据库所有字段(包括 password、token、is_deleted)一股脑查出来,哪怕你只在模板里显示 name 和 avatar。
为什么 $hidden 不管用?
$hidden 只对模型序列化生效(比如 toArray()、toJson()),但前提是:你得先拿到模型实例。一旦用了原生查询或跳过模型层,它就彻底失效。
-
Db::name('user')->select()→ 返回数组,$hidden完全不触发 -
UserModel::field('id,name')->select()→ 字段白名单有效,$hidden不参与 -
UserModel::find(1)→ 模型实例生成,$hidden生效 -
UserModel::withoutField('password')->find(1)→ 当前查询临时屏蔽,返回仍是模型实例,后续可调方法
如何真正避免 SELECT *?
靠模型属性或全局配置拦不住,必须在查询发起时就明确字段。TP6 的 field() 是最直接、最可控的方式。
- 显式声明字段:
UserModel::field('id,name,email,created_at')->select() - 支持别名:
field('id as uid, name as nickname') - 配合
with()关联查询时,关联模型也得单独field(),否则关联表仍可能* - 不要依赖
allowField控制查询字段——它只对写入(save)生效,和SELECT无关
field() 和 withoutField() 怎么选?
field() 是白名单,withoutField() 是黑名单。生产环境强烈推荐白名单。
- 白名单更安全:
field(['id', 'name', 'status'])—— 多一个字段都不会漏出 - 黑名单易遗漏:
withoutField(['password', 'salt', 'remember_token'])—— 新增敏感字段时,容易忘记加进去 -
withoutField()返回的是Collection或数组,不是模型实例,后续不能链式调visible()或触发获取器 - 两者都不影响
Db类查询,Db::name('user')->field(...)->select()才是跨模型通用写法
容易被忽略的坑
很多人以为设了 $hidden 就高枕无忧,结果在导出 Excel、API 调试、日志打印时,敏感字段原样暴露。
-
toArray(true)会绕过$hidden和$visible,慎用 - 手动组装数组(如
['name' => $user->name, 'mobile' => $user->mobile])等于主动放弃所有模型过滤逻辑 - 关联模型没设
field(),主表安全了,附表照样*出来 - 使用
Db::query()原生 SQL 时,字段控制完全靠你自己写死,框架不干预
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











