$hidden仅在模型序列化(toArray/toJson/API Resource)时生效,对日志、广播、dd、关联字段无效;须匹配属性名而非列名;关联需单独隐藏;Resource中遇toArray绕过、直接赋值、$visible白名单时失效。

直接在模型里写 $hidden 数组是最简单、最可靠的起点,但它只在模型被序列化(toArray()、toJson()、API Resource 自动调用)时生效,对日志、事件广播、dd()、关联模型字段完全无效——别指望它“一设就全局安全”。
模型里怎么设 $hidden 才真正起作用
它匹配的是模型属性名,不是数据库列名,也不是访问器别名。比如数据库字段是 password_hash,但你定义了 getPasswordAttribute(),那要隐藏的必须是 password,写 password_hash 就白搭。
-
$hidden字段必须和getAttribute()返回的键名一致,常见漏项:api_token、two_factor_secret、temp_key - 别和
$guarded混:后者管批量赋值(create()/fill()),跟输出无关 -
$casts不影响$hidden优先级——哪怕字段被 cast 成数组,只要在$hidden里,照样不进 JSON - 关联关系名也能放进去,比如
'posts',这样$user->toArray()就不会带出整个 posts 数组
makeHidden() 为什么对 $user->posts 没用
因为 makeHidden() 只修改当前模型实例的原始属性列表,不递归处理集合或嵌套关系。调用 $user->load('posts')->makeHidden(['email']) 后,$user 的 email 确实没了,但每个 Post 实例里的 content 或 user 关联仍完整输出。
- 想临时隐藏关联字段,得单独操作:
$user->posts->makeHidden(['content']) - 更稳妥的做法:在
Post模型里也定义protected $hidden = ['content'] - 多层嵌套(如
posts.comments.user)时,makeHidden()完全不适用,只能靠 Resource 或手动unset
API Resource 里 $hidden 失效的三个典型场景
你以为加了 $hidden 就万事大吉,结果接口返回里敏感字段还在——大概率掉进了这几个坑:
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
- 在
toArray()里写了return $this->resource->toArray():这绕过了你写的逻辑,也绕过了$hidden的二次过滤 - 响应体里直接用了
'user' => $this->user(没调->toArray()):Laravel 会尝试调用jsonSerialize(),但某些中间件或调试工具可能跳过模型可见性检查 - 启用了
$visible白名单模式:$hidden和makeHidden()全部失效,Laravel 只认白名单里的字段
事件广播和日志里 $hidden 压根不触发
这是最常被忽略的泄露点。$user->toArray() 在事件广播中不受 $hidden 约束,绝对不要写 event(new UserCreated($user->toArray()))——密码、token 全都会发出去。
- 改用
$user->only(['id', 'name', 'email']),或在模型里加个toBroadcastArray()方法统一维护 - 如果用了
ShouldBroadcast,broadcastWith()返回的数组就是最终广播内容,必须手工过滤,不能依赖模型自动行为 -
dd()、Log::info($user)、Telescope 日志这些地方,$hidden同样无效;敏感字段只要还在模型实例上,就能被读到
真正麻烦的从来不是“怎么设”,而是“在哪设才管用”。$hidden 是一道窄门,只开在序列化那一刻;门后还有日志、广播、关联嵌套、Resource 构造方式……每个环节都得单独确认是否被覆盖。漏掉任何一个,敏感字段就可能从缝隙里漏出去。










