ThinkPHP 模型 `hidden` 字段我写了数组,API 却把密码原样吐了出去:原来是 `collection` 调用时我搞混了两种隐藏姿势

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 59 浏览 3 回复

昨晚给移动端接口做数据脱敏,明明在模型里写了 `protected $hidden = ['password', 'salt']`,Postman 一测,用户列表里密码字段大摇大摆地躺在 JSON 里。我差点怀疑人生——这字段是叛变了还是 TP 不认账了?

折腾四十分钟,问题出在 查询结果集的处理方式 上。我以为是同一个东西,其实两种写法藏着完全不同的逻辑路径。

❌ 错误写法:先转数组,再套 collection

当时图省事,从别的接口复制了一段代码:

// 控制器里
$list = User::select()->toArray();
return json([
    'code' => 200,
    'data' => collection($list)->hidden(['password'])
]);

看起来没毛病对吧?`collection` helper 函数包一层,再调 `hidden`。但这里 `toArray()` 已经把模型实例变成了纯数组,模型的 `$hidden` 属性在转数组那一刻就已经失效了。后面的 `collection($list)` 其实创建的是 `think\Collection` 普通集合,不是模型集合,它的 `hidden` 方法参数只对该次调用生效,而且我手贱只写了 `['password']`,把 `salt` 漏了——更要命的是,这种写法完全绕过了模型本身的数据过滤机制。

结果:密码、盐值全暴露,字段白名单黑名单统统不生效。

✅ 正确写法:保持模型集合,让 `$hidden` 自己干活

改完后的版本:

// 控制器里
$list = User::select();
return json([
    'code' => 200,
    'data' => $list->hidden(['password', 'salt'])
]);

关键点:不要 `toArray()`。`User::select()` 返回的是 `think\model\Collection` 模型集合,它继承自普通 Collection 但重写了 `hidden` 方法。这时候调用 `hidden`,会先检查 模型自身定义的 `$hidden`,再合并你传入的参数,最终通过模型的 `toArray()` 输出过滤后的结果。

或者更懒一点,完全信任模型定义:

$list = User::select();
return json(['code' => 200, 'data' => $list]);

因为 JSON 序列化时会自动走模型的 `toArray()`,`$hidden` 和 `$visible` 都会生效。我之前是多此一举还搞砸了。

另一个隐蔽变体:分页场景的坑

如果你用分页,错误写法更容易出现:

// ❌ 错的:分页结果先拆再转
$page = User::paginate(10);
$items = $page->items(); // 返回数组!
return json(['data' => collection($items)->hidden(['password'])]);

`paginate()->items()` 返回的是 已经 toArray 过的数组,模型属性早没了。正确做法是直接操作分页对象,或者取原始模型集合:

// ✅ 对的
$page = User::paginate(10);
$page->hidden(['password', 'salt']); // 模型集合支持链式调用
return json(['data' => $page]);

我总结的防翻车检查点

1. 看见 `toArray()` 就警惕:一旦调了,后面所有模型层面的过滤、追加、隐藏都失效,变成裸数组裸奔。

2. `collection()` vs 模型集合:用 `var_dump(get_class($list))` 确认类型,是 `think\model\Collection` 还是 `think\Collection`,两者 `hidden` 实现完全不同。

3. 分页别拆 `items()`:要隐藏字段就在分页对象上操作,或者自定义分页资源类,别半路把模型剥了皮。

4. 模型 `$hidden` 是底线,控制器参数是补丁:敏感字段统一写进模型,控制器临时 `hidden` 只补漏,别当主力用。

这次踩坑让我意识到,TP 里很多"看起来一样"的方法,差一个调用顺序就是安全和裸奔的区别。尤其是做 API 接口,数据出口一定要把好关,别学我,等 Postman 亮出密码那一刻才后背发凉。

你们有没有类似"写法差一行,结果差一界"的经历?欢迎丢出来一起复盘。

评论3
回复 · 3
浪漫的草莓
浪漫的草莓 新手 · #3 ·
已解决,谢谢楼主
安然77
安然77 新手 · #2 ·
同求,期待更新
知夏45
知夏45 新手 · #1 ·
学到了,顶一下
微信客服 微信客服