$hidden 数组仅对数据库原始字段生效,不作用于 append 字段、关联模型或原生查询;需为每层关联模型单独设置,且 toArray(true) 会绕过隐藏逻辑。

模型类里直接写 $hidden 数组最常用,但只对原始字段生效
绝大多数场景下,直接在模型类中声明 protected $hidden = ['password', 'salt', 'token'] 就能屏蔽敏感字段。它作用于 toArray()、toJson()、__toString() 等序列化方法,但仅限数据库查出来的原始字段——append 添加的虚拟字段、关联模型字段、Db::table() 原生查询结果都不受其影响。
常见错误现象:明明写了 $hidden = ['mobile'],返回 JSON 里手机号还在。大概率是用了 append 动态加了 _mobile_masked 字段,而 $hidden 默认不认带下划线的虚拟字段;或者关联模型(如 UserProfile)没单独设 $hidden,导致嵌套数据泄露。
-
$hidden是静态声明,一设就全局生效,无需每次调用 - 字段名必须和数据库字段完全一致,不能带表前缀,也不能写成
'profile.mobile'这种关联路径 - 空数组
[]表示“一个字段都不返回”,不是“返回全部” - 若字段根本没被
field()查出来,$hidden根本不会触发——它只过滤已加载的数据
toArray(true) 会彻底绕过 $hidden,调试时务必注意
$user->toArray() 默认走隐藏逻辑,但一旦传参 true,就会强制返回所有属性(包括被 $hidden 拦下的),这是 ThinkPHP 的反直觉设计。很多开发者在调试或封装分页响应时手滑写成 toArray(true),结果敏感字段全量暴露。
更隐蔽的问题是:有人手动拼响应体,比如 ['data' => $user->toArray()] 看似安全,但如果中间某处写了 $user->toArray(true),后续所有 toJson() 都会沿用这个“全显”状态——因为 toJson() 内部就是调的 toArray()。
立即学习“PHP免费学习笔记(深入)”;
- 调试时优先用
var_dump($user->toArray()),而不是dd($user),后者显示的是原始模型对象,不反映最终输出 - 永远别直接
json_encode($user),这完全跳过$hidden和模型序列化流程 - 如果必须用
toArray(true)(比如导出原始数据做日志),记得之后立刻重置:调用一次$user->toArray()即可恢复默认行为
关联模型的字段要各自设 $hidden,不存在继承或穿透
主模型设了 $hidden = ['password'],不代表关联的 Profile 或 Avatar 模型里的敏感字段也会被隐藏。ThinkPHP 序列化时对每个模型实例单独处理,$hidden 不会跨模型传递。
典型踩坑点:用 with(['profile', 'posts']) 预载入后,$user->toArray() 返回的 profile 对象里依然有 id_card 字段——只因 Profile 类里没写 protected $hidden = ['id_card']。
- 每一层关联模型都必须独立定义
$hidden,哪怕字段名重复 - 确保关联方法返回的是模型实例,例如
return $this->hasOne(Profile::class),而不是return $this->hasOne(Profile::class)->field([...])后手动转数组 - 多层级嵌套(User→Profile→Avatar)能否逐层隐藏,取决于每一层是否正确定义了
$hidden,跟“深度”无关
需要动态控制时,别硬靠 $hidden,改用 hidden() 或 visible() 链式方法
当隐藏逻辑依赖用户角色(如管理员可见邮箱,普通用户不可见)、请求参数或业务状态时,静态 $hidden 就不够用了。这时候得在查询阶段动态干预,而不是靠模型属性。
例如:User::find(1)->hidden(['email'])->toArray() 只对这次结果生效;而 visible() 更适合“白名单”场景——字段多、隐藏少时,明确列出允许返回的字段比挨个写 hidden 更安全,也避免新增字段漏加导致泄露。
-
hidden()返回的是 Collection 或数组,不再是模型实例,后续不能调save()或关联方法 -
field()在 TP6.1+ 必须传数组,field('id,name')这种字符串写法已废弃 - 如果用了
writeOnly(TP6.1+ 支持),记得配合$hidden使用,否则密码字段可能仍会出现在某些序列化路径中 - 最容易被忽略的出口:日志记录、异常上下文、缓存序列化、第三方 SDK 接口透传——这些地方往往绕过控制器的
hidden()调用
$hidden,而是确认它在哪一层、哪一条调用链上失效了。尤其是当 append、关联预载入、手动数组拼装、toArray(true)、json_encode() 混在一起时,一个环节漏掉,敏感字段就可能直接裸奔。



















