根本原因是toJson()直接序列化$data中的原始值,不触发获取器或$type转换;正确做法是重写toArray()统一格式化日期,或用$append+getAttr定义只读格式字段。

ThinkPHP6 模型调用 toJson() 时日期格式化不生效,根本原因在于:toJson() 默认走 PHP 原生 json_encode(),不会自动触发模型的获取器(getAttr),也不会应用 $type 类型转换或 $append 字段逻辑——它只序列化模型当前的属性值(即 $this->data 中已有的内容),而这些值往往还是原始时间戳或未格式化的字符串。
要让 toJson() 输出符合预期的日期格式(如 "2024-10-03 14:20:00" 而非时间戳或错误时间戳),需从序列化入口干预,而不是依赖模板或控制器手动处理。
✅ 正确做法:重写模型的 toJson() 或 toArray() 行为
TP6 不提供全局 serializeDate(那是 Laravel 的),但可通过以下两种可靠方式控制:
1. 在模型中重写 toArray(),统一注入格式化逻辑
toJson() 内部会先调用 toArray(),所以只要 toArray() 返回的是你想要的结构,toJson() 自然就对了。
立即学习“PHP免费学习笔记(深入)”;
public function toArray()
{
$data = parent::toArray();
// 对日期字段做安全格式化(仅当有值时)
if (!empty($data['create_time'])) {
$data['create_time'] = date('Y-m-d H:i:s', (int)$data['create_time']);
}
if (!empty($data['update_time'])) {
$data['update_time'] = date('Y-m-d', (int)$data['update_time']);
}
return $data;
}⚠️ 注意:
- 判断
!empty()防止date()处理null或0报警告; - 强制
(int)转换,避免字符串时间戳被误解析; - 不推荐在
getCreateTimeAttr里直接返回字符串(会影响后续查询、排序),这里是在「最终输出层」做转换,职责清晰。
2. 使用 $append + 对应的 getxxxAttr 方法(推荐用于 API 场景)
定义一个只读格式化字段,比如 create_time_format,并在模型中声明:
protected $append = ['create_time_format'];
protected function getCreateTimeFormatAttr($value, $data)
{
$time = $data['create_time'] ?? null;
if (empty($time)) {
return '';
}
return date('Y-m-d H:i:s', (int)$time);
}这样调用 $model->toJson() 时,只要 create_time_format 没被 hidden 过滤,就会自动出现在 JSON 中,且值已格式化。
✅ 优势:
- 不污染原始字段,
$model->create_time仍可参与查询、比较; - 支持按需添加多个格式变体(如
create_date,create_time_only); - 兼容分页(
paginate()->toArray()同样生效)。
❌ 常见错误 & 为什么无效
只写了
getCreateTimeAttr却没配$append或没在toArray()中显式访问该字段
→toJson()不会主动调用它,除非你手动$model->create_time_format或toArray()里用了。-
数据库字段是
datetime类型,却没在模型中声明$typeprotected $type = [ 'create_time' => 'datetime', 'update_time' => 'datetime', ];→ 即使加了
$type,TP6 的toJson()仍不会自动格式化成字符串,它只是把datetime转成Carbon实例;而Carbon实例被json_encode时默认转成时间戳(不是你想要的'Y-m-d H:i:s')。所以仍需toArray()或getAttr干预。 在控制器里
json($model->toArray())却忘了重写toArray()
→ 和直接$model->toJson()效果一样,没解决问题。
? 补充:导出 Excel 或其他场景也适用
如果还要支持 Excel 导出,建议统一在 toArray() 中处理,或封装一个 toExportArray() 方法,避免重复逻辑。例如:
public function toExportArray()
{
$data = $this->toArray();
$data['create_time'] = $this->create_time ?
Carbon::createFromTimestamp($this->create_time)->format('Y/m/d H:i') : '';
return $data;
}这样导出和 API 输出都能复用同一套格式规则。
不复杂但容易忽略:关键不在“怎么写格式化代码”,而在于明确 toJson() 的触发边界——它不执行获取器,只序列化已有数据。把格式化逻辑放在 toArray() 或 $append 字段中,才是 TP6 下稳定可控的做法。



















