ThinkPHP 6 的 $hidden 属性是静态类属性,PHP 加载时即固化,运行时修改 $model->hidden 无效;它仅作用于 toArray()/toJson() 等序列化方法,不干预 SQL 查询,且不适用于关联模型、Db 查询、toArray(true) 或手动拼数组等场景。

ThinkPHP 6 的 $hidden 属性只在模型序列化时起作用,不能动态修改,也不能控制数据库查询本身;真要按需隐藏字段,得用 visible()、withoutField() 或查询层过滤。
为什么直接改 $model->hidden 没用
因为 $hidden 是模型类的 静态属性,PHP 在类加载时就固化了。你在控制器里写 $user->hidden = ['password'],对 toArray()、toJson() 都不生效——这些方法读的是类定义里的原始值,不是运行时改的实例属性。
-
toArray(true)会彻底绕过$hidden和$visible,连白名单都不认 - 手动拼数组(如
['password' => $user->password])也完全跳过模型过滤逻辑 - 关联模型的字段不会被父模型的
$hidden自动过滤,必须各自配置
用 visible() 替代 $hidden 更可控
白名单思维比黑名单更安全:默认一个字段都不吐,显式声明才给。模型里删掉 $hidden,改用:
protected $visible = ['id', 'username', 'email'];
运行时可动态切换:
立即学习“PHP免费学习笔记(深入)”;
-
$user->visible(['id', 'username'])->toArray()—— 临时覆盖 - 不同接口配不同白名单:
config('api.v1_visible')、config('admin.visible') - 注意:
with()关联查询时,关联模型也要单独设$visible,不会继承
查询阶段就砍掉字段最干净
别等数据查出来再过滤,直接在 SQL 层减少传输和内存开销:
- 先拿到所有字段:
$allFields = (new UserModel())->getSchema()['fields'] ?? [] - 读配置决定要隐藏哪些:
$hideFields = config('api.hidden_fields', []) - 构造查询:
UserModel::field(array_diff($allFields, $hideFields))->select() - 别用
->hidden(['password'])链式调用——它只影响后续toArray(),SQL 还是全查
导出、Db 查询、append 字段这些场景容易漏
它们压根不走模型的 $hidden 流程:
-
Db::name('user')->select()返回原生数组,$hidden完全无效 -
append加的虚拟字段(如_mobile_masked)默认不受$hidden管控,得显式加进$hidden数组 - Excel 导出若用
$list->toArray(),要确认$list是模型集合;若是Db::select()结果,就得手动array_map()过滤
真正麻烦的从来不是“怎么写代码”,而是字段权——哪个接口能看什么字段,得靠配置驱动、分层拦截,而不是指望一个 $hidden 数组兜底。



















