ThinkPHP模型中JSON字段需手动声明type类型才能自动解码,hidden/visible仅在toArray()等序列化方法生效,field()查询会绕过隐藏逻辑,append支持运行时追加但需预定义获取器,JSON更新需force()或data()确保变更检测。

模型里用 json 类型字段,查出来却是字符串不是数组?
ThinkPHP 的模型默认不会自动解码 JSON 字段,哪怕数据库字段类型是 JSON 或 TEXT 存的 JSON 字符串,select() 或 find() 拿到的仍是原始字符串。这不是 Bug,是设计上留出控制权——避免误解析非标准 JSON 或性能损耗。
实操建议:
- 在模型类中给该字段加
type声明:protected $type = ['extra_info' => 'json']; - 确保字段值是合法 JSON(双引号、无尾逗号),否则解码失败会返回
null,且不报错 - 如果字段名带下划线(如
user_config),也要严格按实际字段名写进$type数组,大小写敏感 - 5.1+ 支持嵌套 JSON 解析(如
'settings.info' => 'json'),但仅限关联字段或使用withAttr时生效
hidden 和 visible 不起作用?字段还在输出里
这两个属性只对模型的 toArray()、toJson()、__toString() 以及控制器中直接 echo $model 生效;如果手动调用 getAttr()、遍历 $model->data 或用了 field() 查询,就完全绕过了隐藏逻辑。
常见错误现象:
立即学习“PHP免费学习笔记(深入)”;
- API 返回里依然看到
password字段,但模型已设protected $hidden = ['password'] - 用
User::where(...)->field('id,name')->select(),结果toArray()后字段没被隐藏——因为field()是查询层控制,$hidden是序列化层控制,两者不叠加
正确做法:
- 优先用
hidden控制输出,但确认你调用的是模型实例的toArray()(比如$user->toArray()),而不是$user->data - 需要动态控制时,改用
append()+visible组合,例如:$user->visible(['id', 'name'])->append(['full_name'])->toArray() - 若走 Db 查询(非模型),
hidden/visible完全无效,得靠 PHP 层unset()或array_intersect_key()
想追加字段但只在某些接口用,append 写死在模型里太死板
append 支持运行时追加,不用改模型定义。但要注意:追加的字段必须已在模型中定义过获取器(getXXXAttr)或通过 withAttr 注册,否则追加无效。
使用场景:
- 用户列表接口不需要头像 URL,但详情页需要——避免所有请求都拼一次
- 后台导出 Excel 时要加「部门名称」,但 API 列表不加
实操建议:
- 先在模型里写好获取器:
public function getAvatarUrlAttr($value, $data) { return $data['avatar'] ? '/upload/' . $data['avatar'] : ''; } - 运行时追加:
$user->append(['avatar_url'])->toArray(),注意这里是下划线命名,和获取器名对应 - 如果获取器参数依赖外部数据(如当前域名),建议把逻辑抽到服务类,模型里只做简单封装,否则测试和复用困难
JSON 字段更新后,save() 没触发变更检测,数据库没更新
ThinkPHP 的模型变更检测基于原始数据快照($origin),而 JSON 字段解码后是数组,PHP 数组比较用的是引用或 ===,深层结构变化可能被忽略。
典型表现:
- 修改了
$user->extra_info['theme'] = 'dark',再调$user->save(),SQL 里没出现extra_info字段 - 用
json_encode()手动转回字符串再赋值,反而能触发更新
根本原因:模型认为 $user->extra_info 是个数组,而原始快照里存的是字符串,类型不同导致无法比对差异。
解决办法:
- 强制标记为变动:
$user->force()->save(),跳过变更检测(适合确定要更新的场景) - 更新前重载字段:
$user->extra_info = json_encode($user->extra_info);,保持类型一致 - 更稳妥的方式:用
data()批量赋值,绕过属性访问器:$user->data(['extra_info' => json_encode([...])])->save()
复杂点在于,JSON 字段既是数据载体又是结构容器,模型层很难兼顾语义与性能;一旦业务里频繁读写子字段,不如拆成独立关系表——别硬扛。


















