Laravel 10中JSON字段写入Carbon时间出现无效值问题排查
问题
在Laravel 10项目中,需要将登录用户信息及登录时间写入JSON字段。代码中使用Carbon::now($timezone)获取时间,日志显示时区(Europe/Kiev)和时间均正常,但数据库data字段中的online_from值却无效。相关代码、日志输出、表迁移及建表语句如下:
业务代码
\Log::info(varDump($timezone, ' -1 $timezone::')); \Log::info(varDump(Carbon::now($timezone), ' -1 BEFORE EXIT Carbon::now($timezone)::')); return KeyData::create([ 'key' => $key, 'data' => [["user_id" => $loggedUserId, "online_from" => Carbon::now($timezone), 'temp_field' => 'temp_value']], 'expired_at' => Carbon::now($timezone)->addSeconds($cachingTimeInSeconds) ]);
日志输出
-1 $timezone:: : Europe/Kiev [2024-05-23 10:03:10] local.INFO: (Object of Carbon\Carbon) : -1 BEFORE EXIT Carbon::now($timezone):: : Array ( [ * endOfTime] => [ * startOfTime] => [ * constructedObjectId] => 000000000000055b0000000000000000 [ * localMonthsOverflow] => [ * localYearsOverflow] => [ * localStrictModeEnabled] => [ * localHumanDiffOptions] => [ * localToStringFormat] => [ * localSerializer] => [ * localMacros] => [ * localGenericMacros] => [ * localFormatFunction] => [ * localTranslator] => [ * dumpProperties] => Array ( [0] => date [1] => timezone_type [2] => timezone ) [ * dumpLocale] => [ * dumpDateProperties] => [date] => 2024-05-23 10:03:10.253696 [timezone_type] => 3 [timezone] => Europe/Kiev )
表迁移代码
Schema::create('key_data', function (Blueprint $table) { $table->id(); $table->string('key', 255); $table->json('data')->nullable(); $table->dateTime('expired_at'); $table->timestamp('created_at')->useCurrent(); $table->index(['key', 'expired_at'], 'key_data_key_expired_at_index'); });
建表语句
CREATE TABLE `key_data` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `data` json DEFAULT NULL, `expired_at` datetime NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `key_data_key_expired_at_index` (`key`,`expired_at`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
原因分析
Laravel处理JSON字段时,会自动将数组序列化为JSON字符串,但Carbon对象无法直接被序列化为有效的JSON时间格式。直接将Carbon实例放入data数组中时,Laravel会把它转为包含内部属性的数组(如日志显示的Carbon内部结构),JSON序列化后会保留这些无用的内部字段,导致online_from存储值无效。
而expired_at字段正常,是因为它被定义为dateTime类型,Laravel ORM会针对日期字段自动将Carbon对象转换为数据库可识别的datetime格式,但JSON字段没有这种自动转换逻辑。
修复方案
需将Carbon对象显式转换为字符串格式,推荐使用标准格式确保JSON序列化后是有效的时间字符串。
方案1:使用ISO 8601标准格式(推荐)
return KeyData::create([ 'key' => $key, 'data' => [["user_id" => $loggedUserId, "online_from" => Carbon::now($timezone)->toIso8601String(), 'temp_field' => 'temp_value']], 'expired_at' => Carbon::now($timezone)->addSeconds($cachingTimeInSeconds) ]);
方案2:自定义时间格式
如果需要特定格式(如Y-m-d H:i:s),使用format()方法:
return KeyData::create([ 'key' => $key, 'data' => [["user_id" => $loggedUserId, "online_from" => Carbon::now($timezone)->format('Y-m-d H:i:s'), 'temp_field' => 'temp_value']], 'expired_at' => Carbon::now($timezone)->addSeconds($cachingTimeInSeconds) ]);
方案3:模型中定义自动转换(更优雅)
在KeyData模型中,通过Attribute自定义data字段的转换逻辑,自动处理Carbon对象:
class KeyData extends Model { protected function data(): Attribute { return Attribute::make( get: fn ($value) => json_decode($value, true), set: function ($value) { if (isset($value[0]['online_from']) && $value[0]['online_from'] instanceof Carbon) { $value[0]['online_from'] = $value[0]['online_from']->toIso8601String(); } return json_encode($value); } ); } }
后续写入data字段时无需手动转换Carbon对象,模型会自动处理。
内容的提问来源于stack exchange,提问作者mstdmstd
相关产品推荐
相关产品推荐

