ThinkPHP模型反序列化必须用Model::instance()还原状态,而非new或create;需配置$type类型转换确保JSON字段正确解析;关联数据须手动setRelation挂载;否则save()会误作新增导致重复插入。

ThinkPHP 模型反序列化失败:toArray() 后再 new 不等于还原模型
直接用 json_decode() 或 unserialize() 恢复出来的不是 ThinkPHP 模型对象,只是普通 PHP 数组或 StdClass 实例。调用 save()、relation()、isUpdate 等方法会报错或静默失效。
真正能“还原”模型行为的,必须走 ThinkPHP 的实例化流程,而不是手动 new:
-
Model::create($data)是最常用方式,但只适用于新增场景(isUpdate默认为 false) - 若需还原带主键、状态、关联标记的“已存在”模型,得用
Model::instance($data)—— 它会设置exists = true、pk值,并保留原始数据快照 - 注意:
instance()不触发事件、不校验、不初始化关联,仅构造基础状态
JSON 字符串转模型对象:别跳过 withAttr 和类型转换
从接口接收 JSON 时,前端传的 "status": "1" 是字符串,但模型里 status 可能是整型或布尔型字段。直接 create() 过去,status 会以字符串存入数据库,后续条件查询容易出错。
解决办法是在模型定义中显式声明类型转换,否则 JSON 解析后不会自动处理:
立即学习“PHP免费学习笔记(深入)”;
protected $type = [
'status' => 'integer',
'is_hot' => 'boolean',
'tags' => 'json'
];
- 没配
$type,create(['status' => '0'])存进去就是字符串'0',不是整数0 -
json类型字段会自动json_decode成数组,但前提是字段名在$type里明确写了 - 如果字段有
withAttr访问器(比如getCreateTimeAttr),它只在读取时生效,不影响写入反序列化过程
反序列化后关联数据丢失?with() 不会自动恢复
模型转 JSON 时调用 toJson() 或 toArray(),默认不包含关联数据,除非你显式写了 with(['user', 'category'])。反过来,把含 user 字段的 JSON 再塞回 create(),ThinkPHP 不会自动识别这是关联并挂载到 $model->user 属性上。
想让关联“可逆”,得手动处理:
- 先用
Model::create($data, ['validate' => false])构建主模型 - 再对每个关联字段单独赋值:
$model->user = User::create($data['user'] ?? []); - 或者更稳妥:用
$model->setRelation('user', User::create(...)),避免属性覆盖风险 - 切记:关联模型也得自己处理
$type和instance()状态,否则嵌套层级一深就全变数组
缓存里存模型 JSON,读出来直接改字段再 save() 很危险
从 Redis 读出 JSON,json_decode($json, true) 得到数组,再 Model::create($arr),这个模型的 origin 数据是空的。此时调用 save(),ThinkPHP 会当成新增记录插入,而不是更新 —— 即使数组里有 id 字段。
正确做法只有两个:
- 用
Model::instance($arr),它会把id设为pk并设exists = true,后续save()才走 UPDATE - 或先查库:
$model = Model::find($arr['id']); $model->save($arr);,适合需要校验或事件的场景 - 千万别用
create()+save()组合,这是最隐蔽的数据重复插入来源
模型反序列化的坑不在语法,而在状态机:ThinkPHP 模型不是数据容器,它是带生命周期、快照、状态标记的对象。漏掉 instance()、忽略 $type、绕过关联挂载,都会让“还原”变成假还原。


















