ThinkPHP模型中$hidden属性仅在toJson()等序列化方法生效,直接toArray()或json_encode()会绕过;动态脱敏应使用getAttr访问器;导出等场景需手动处理,因toArray(true)和getData()不触发访问器。

ThinkPHP模型中用hidden属性屏蔽字段不生效?
常见现象是加了protected $hidden = ['password', 'id_card'];,但toArray()或API返回里还是能看到——根本原因是没走模型的序列化逻辑。ThinkPHP默认只在toJson()、toJSON()或调用__toString()时才触发$hidden过滤,直接json_encode($model)或var_dump($model->toArray())会绕过它。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 统一用
$model->toJson()输出,别手写json_encode() - 如果必须用
toArray(),改用$model->visible(['name', 'email'])->toArray()显式声明白名单 - 注意:关联模型不会自动继承父模型的
$hidden,每个关联模型得单独配
想对字段做动态脱敏(比如手机号中间变*),该在哪层处理?
不能塞在$hidden里——那是纯隐藏,不是变形。最佳位置是模型的getAttr访问器(Accessor),它在读取字段时触发,且对toArray()、toJson()、模板输出都生效。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在模型里加方法:
public function getPasswordAttr($value) { return '******'; } - 手机号脱敏示例:
public function getPhoneAttr($value) { return substr($value, 0, 3) . '****' . substr($value, -4); } - 注意:访问器名必须是
get + 字段名首字母大写 + Attr,否则不触发;数据库字段名是phone,就叫getPhoneAttr - 性能影响小,但别在访问器里查库或调远程接口
API返回时部分接口要脱敏、部分不用,怎么灵活控制?
硬编码$hidden或访问器是全局的,没法按接口区分。得靠「序列化场景」(ThinkPHP 6.1+ 支持)或手动切换模型状态。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 定义场景:
protected $schema = ['scene' => ['api_safe' => ['hidden' => ['password', 'id_card']]]]; - 调用时指定:
$model->hidden(['password'])->toArray()临时覆盖 - 更稳妥的做法:在控制器里用
collection包装后统一处理,比如$list->each(function ($item) { $item->phone = hide_phone($item->phone); }); - 别依赖中间件统一脱敏——字段逻辑分散,后期难维护
脱敏后导出Excel或生成PDF还显示明文?
因为导出通常绕过模型访问器,直接读$model->getData()或$model->toArray(true)(true表示忽略访问器)。这是最容易被忽略的一环。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 导出前手动处理:
$data = $model->toArray(); $data['phone'] = hide_phone($data['phone']); - 或者封装一个
exportArray()方法,在模型里统一处理敏感字段 - 注意:
toArray(true)和getData()结果一致,都不走访问器,别误以为“只是不转JSON”就安全
脱敏不是加个$hidden就完事,关键在数据流出的每一个出口——API、导出、日志、调试打印,都得单独确认是否经过访问器或显式处理。



















