ThinkPHP 6 升级后模型字段报错主因是默认严格字段检测且不自动推导表名,需显式声明 $table 和 $deleteTime,并区分 $type(写入转换)与 $cast(读取类型),软删除需手动启用且字段类型须为 NULLable DATETIME/INTEGER。

ThinkPHP 6 升级后 model 找不到字段报错
升级到 ThinkPHP 6 后,老项目里用 Db::name('user')->select() 能查出数据,但一换 UserModel::all() 就抛 Unknown column 'id' in field list ——不是数据库缺字段,是模型没映射上。TP6 默认开启严格字段检测,且不再自动读取表结构缓存。
- 检查
UserModel是否显式声明了protected $table = 'user';;TP6 不再根据类名自动推导表名(如UserModel≠user) - 确认
protected $schema没被误设为空数组或错误字段列表;有定义就会跳过自动获取,且不校验一致性 - 临时关闭字段验证:在模型中加
protected $autoWriteTimestamp = false;并设置protected $schema = [];,再试一次——如果 OK,说明问题出在 schema 同步机制上
TP5.1 的 type 转 TP6 的 cast 字段类型失效
原来在 TP5.1 的模型里写 protected $type = ['create_time' => 'datetime'],升级后时间字段变字符串、json 字段不自动解码——TP6 把类型转换逻辑从 $type 搬到了 $cast,且语义和行为都变了。
-
$type在 TP6 中仅用于数据库写入前的格式转换(如日期转时间戳),不负责读取后的反序列化 -
$cast才控制读取后字段的 PHP 类型,比如'content' => 'json'或'status' => 'integer' - 注意
json类型字段必须配JSON数据库类型(MySQL 5.7+),否则$cast会静默失败,返回原始字符串
迁移脚本里用 Db::raw() 插入旧数据,主键自增错乱
批量迁移时习惯用 Db::insertAll($data),但发现新表主键重复、ID 跳号严重——TP6 的 insertAll 默认不返回最后插入 ID,且不感知事务中已存在的自增偏移。
- 避免直接用
Db::raw('NOW()')填充时间字段:TP6 对 raw 表达式不做任何处理,若目标字段是DATETIME但值为字符串格式(如'2023-01-01'),可能被 MySQL 截断或转成 0000-00-00 - 主键冲突时优先用
Db::replace()替代insertAll,它能自动处理INSERT ... ON DUPLICATE KEY UPDATE - 若必须保留原 ID,迁移前执行
DB::execute("ALTER TABLE `user` AUTO_INCREMENT = 10000")(把值设为最大旧 ID +1),否则后续新增记录会从 1 开始抢号
软删除字段 delete_time 在 TP6 中默认不生效
TP5.1 里只要模型继承 SoftDelete trait 就自动过滤已删数据,TP6 需要额外两步激活,否则 select() 和 find() 全部返回软删记录。
立即学习“PHP免费学习笔记(深入)”;
- 必须在模型中显式启用:加上
use think\model\concern\SoftDelete;和use SoftDelete;,再声明protected $deleteTime = 'delete_time'; - 注意字段类型:TP6 要求
delete_time是NULL允许的DATETIME或INTEGER,不能是VARCHAR;否则软删标记写入失败,查询时条件永远为 false - 关联查询默认不继承软删除状态,例如
User::with('posts')->find(1)会拉出已软删的 posts,得手动在关联定义里加->whereNull('delete_time')
$table 和 $deleteTime 显式声明,还有 $cast 和 $type 的分工——这两处一错,数据看着在,其实根本没进业务逻辑。


















