TP8移除了type/cast对时间字段的读取格式化能力,仅保留写入转换;必须用命名规范的访问器(如getCreateTimeAttr)显式格式化,或通过append添加虚拟字段控制输出。

TP6 升级到 TP8 后,原来用 type 或 cast 配置的时间字段自动格式化会失效——不是 bug,是框架主动移除了该行为。你不能再靠模型配置让 $user->create_time 直接返回字符串。
TP8 中 type 和 cast 对时间字段已无读取时格式化能力
TP8 彻底剥离了「存储类型转换」和「API 输出格式化」的耦合。比如你在模型里写:
protected $type = [
'create_time' => 'datetime:Y-m-d H:i:s',
];
这行代码在 TP8 中只影响写入(把字符串转为 Carbon 实例再存),读取时仍原样返回 Carbon 实例,不会自动 toString 或 format。很多人升级后发现接口返回的是对象或时间戳整数,就是这个原因。
- TP6 的
type在读取时会触发格式化(有副作用);TP8 仅保留写入转换语义 -
cast在 TP8 中也仅用于属性类型声明(如转 int/float/string),不处理日期格式 - 若字段数据库类型是
datetime,TP8 默认用Carbon::parse()构造实例,但不会进一步调用format()
必须用访问器(Accessor)显式控制输出格式
这是目前唯一可靠、可预测的方式。访问器名必须严格匹配命名规范,否则不触发:
- 字段名是
create_time→ 访问器必须叫getCreateTimeAttr - 字段名是
updated_at→ 访问器必须叫getUpdatedAtAttr - 不要在访问器里调用
$this->getAttribute('create_time'),会递归报错
示例:
public function getCreateTimeAttr($value)
{
return $value ? $value->format('Y-m-d H:i:s') : null;
}
注意:$value 此时已是 Carbon 实例(非字符串或时间戳),直接调用 format 即可。
想同时暴露原始值和格式化值?用 append + 虚拟字段
如果前端既需要时间戳做计算,又需要字符串做展示,别覆盖原字段,而是新增一个虚拟字段:
- 在模型中定义
protected $append = ['create_time_text']; - 定义对应访问器
getCreateTimeTextAttr - 原
create_time字段保持 Carbon 实例不变,供逻辑使用 - 加到
hidden列表可隐藏原始字段,避免重复传输
这样 API 返回结构就清晰可控:
"create_time": "2026-09-04 21:04:00", "create_time_timestamp": 1756981440, "create_time_text": "2026-09-04"
升级后最容易忽略的一点:TP8 不再“悄悄帮你格式化”,所有输出都得你明确决定——包括空值怎么处理、时区是否要切换、毫秒要不要保留。依赖隐式行为的旧代码,这里最可能崩。

















