Laravel 7 之前,我们序列化时间,比如数据库库里的 timestamp 字段返回的都是如下
"created_at": "2020-07-05 12:00:11", "updated_at": "2020-07-05 12:00:11",
但是,但是,一定你把 Laravel 升到 7 这个以上的版本,就会出现下面这种
"created_at": "2020-07-05 12:00:11.0329000Z", "updated_at": "2020-07-05 12:00:11.0329000Z",
我查咧,后面那个 .329000Z 是怎么出现的咧,算了,这里说起来一言难尽,我们先找找解决办法吧。
恩,通常呢,网上的通用解决方案是这样解决的
app\Models\Product.php
<?php namespace App\Models; use Auth, Lock, Cache; use DateTimeInterface; class Product extends Model { // .....此处省略骂人的话 protected function serializeDate(DateTimeInterface $date) { return $date->format('Y-m-d H:i:s'); } // .... 此处省略发牢骚的话 }
简单来说,就是两步走
-
引用相关类
DateTimeInterface类,这个类是 PHP5.8 之后自带的,就是为了解决一些遗留问题留下的 -
重写
serializeDate()方法,这个方法,恩,Laravel7 之前我不知道它是怎么用的,因为我没有旧项目了等等咧,我去找找看
差一点的解决方法呢,就是
<?php namespace App\Models; use Auth, Lock, Cache; use DateTimeInterface; class Product extends Model { // .....此处省略骂人的话 protected function serializeDate(DateTimeInterface $date) { return $date->format('Y-m-d H:i:s'); } // .... 此处省略发牢骚的话 }
如果你都继承自一个基础的 Model 类,当然是上面的思路比较好了,如果不是,恩,下面的比较快,毕竟哪个出问题解决哪个就是了。
这个查找流程不复杂,几分钟搞定吧
当然了,得有个前提,就是你知道一个框架出了问题,内容肯定是去框架文档里
你得熟知框架的大部分代码和逻辑是怎么来的。比如吧,你翻烂了手册,肯定对下面这个有映象
###Appending Values To JSON
是 Eloquent: Serialization 中如何将某个数据追加到 JSON 序列化的结果中
### Date Serialization Customizing The Default Date Format You may customize the default serialization format by overriding the serializeDate method: /** * Prepare a date for array / JSON serialization. * * @param \DateTimeInterface $date * @return string */ protected function serializeDate(DateTimeInterface $date) { return $date->format('Y-m-d'); } Customizing The Date Format Per Attribute You may customize the serialization format of individual Eloquent date attributes by specifying the date format in the cast declaration: protected $casts = [ 'birthday' => 'date:Y-m-d', 'joined_at' => 'datetime:Y-m-d H:00', ];
案情的真相是什么呢
首先哦,我提两个猜想,让大家猜猜,第一个是 created_at 和 updated_at 的类型 ? 另一个是你说起的这个类型使用了系统自建的哪个类型或接口或 trait ?
第一个答案呢,大家就呼之欲出了,对了 var_dump($model->updated_at); 就知道结果了
```Illuminate\Support\Carbon Illuminate\Support\Carbon
`Illuminate\Support\Carbon` 一看就知道使用了门面模式,哈哈 ```<?php <?php namespace Illuminate\Support; use Carbon\Carbon as BaseCarbon; class Carbon extends BaseCarbon { // }
所以第一个谜题的结果是啥呢,是 Carbon\Carbon
那我们继续来看第二个谜题,我们直接看源码吧,这样比较好
<?php use DateTime; use DateTimeInterface; // .... 次数省略一大堆文字 class Carbon extends DateTime implements CarbonInterface // .... 此处省略一大堆文字
接下来到这里是不是看起来像断头台了,其实不是的,我们还要继续看两个东西 DateTime 和 CarbonInterface
-
DateTime看 PHP 手册就好,它是这样解释的DateTime implements DateTimeInterface {而
DateTimeInterface就没有继续继承自其它接口了DateTimeInterface is meant so that both DateTime and DateTimeImmutable can be type hinted for. It is not possible to implement this interface with userland classes.
大概的意思就是说
DateTimeInterface是DateTime和DatetimeImmutable的类型提示接口,该接口只能用于类型提示,不能被任何接口所实力化我要不要找个时间来解释解释 PHP 的类型提示是怎么实现的,我还得抽出时间来讲讲 PHP 的类型提示是怎么做的
看到这里就很明了了,哈哈,其实我们接下来两条路,就是搜索
DateTime和DatetimeImmutable等等,不要以为最快捷的方法是是在
vendor目录搜索,哈哈,最简单的方案当然是在vendor/laravel/framework/src/Illuminate/Database/Eloquent目录搜索啦,那个,永久了大家都会知道的然后你就能在Illuminate\Database\Eloquent\Concerns\HasAttributes找到突破口了。 -
Illuminate\Database\Eloquent\Concerns\HasAttributes下面呢则有一个这么判断的protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { // ....此处省略... if ($attributes[$key] && ($value === 'date' || $value === 'datetime')) { $attributes[$key] = $this->serializeDate($attributes[$key]); } if ($attributes[$key] && $this->isCustomDateTimeCast($value)) { $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]); } if ($attributes[$key] && $attributes[$key] instanceof DateTimeInterface && $this->isClassCastable($key)) { $attributes[$key] = $this->serializeDate($attributes[$key]); } // ..... 此处省略 } return $attributes; }这个转换呢把有关时间的转换用三种来区分 1. Laravel 框架自定义的
date和datetime2. 自定义的DateTime转换属性 3. PHP 内建的DateTimeInterface且该接口是以类形式被转换的顺着这个意思,我们找到了以下几个大块内容,下面的是 Laravel 框架自带的
datetime和date转换protected static $primitiveCastTypes = [ 'array', 'bool', 'boolean', 'collection', 'custom_datetime', 'date', 'datetime', 'decimal', 'double', 'float', 'int', 'integer', 'json', 'object', 'real', 'string', 'timestamp', ];然后继续看
isCustomDateTimeCast()函数protected function isCustomDateTimeCast($cast) { return strncmp($cast, 'date:', 5) === 0 || strncmp($cast, 'datetime:', 9) === 0; }
最后的
isDateCastable()protected function isDateCastable($key) { return $this->hasCast($key, ['date', 'datetime']); }看到这里我们我们是不是快要把持不住了,赶紧啊查查
HasAttributes有在哪里用到。!!!!
Model!!!
Model!!!
Model兄弟们啊,简单教程的兄弟们啊,我终于在我们经常用的某个类找到了
在我们的自定义模型中经常会这么用
use Illuminate\Database\Eloquent\Model; class Bilibili extends Models {};
-
不要太过兴奋了,大家,因为还有最后的证明,我们重新梳理下上面的代码,上面的代码是说在三种模式下都会调用
Model类的$this->serializeDate($attributes[$key]);
方法,那么我们就再来查查这个
serializeDate这个方法。一番查找,其实不用查找了,自动跳转吧,还是那个
HasAttributes里面protected function serializeDate(DateTimeInterface $date) { return Carbon::instance($date)->toJSON(); }
-
验证啦,真的验证啦。到这里,其实都快闭环了,可是我们还得继续,还得继续。 我们再来查查
toJSON()方法。这次还是老惯例,直接查vendor目录得到下面的代码。不要任何吹风之力,你就能看到
public function toJSON() { return $this->toISOString(); } -
继续顺藤摸瓜看
toISOString还是在同样的类下面发现public function toISOString($keepOffset = false) { if (!$this->isValid()) { return null; } $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY'; $tzFormat = $keepOffset ? 'Z' : '[Z]'; $date = $keepOffset ? $this : $this->copy()->utc(); return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat"); }
其实,这也是 PHP 的一大缺陷,比如
DateTime类就就是下面这种。后面没啥激情了是因为,其实我一开始就知道这个是 JavaScript 的方法,不信的话你运行下面的代码> (new Date()).toISOString() < "2020-07-08T15:55:58.595Z"
那为啥有这么大的区别,是因我我的项目里把
config/app.php设置了timezone的值'timezone' => 'Asia/Shanghai'画了一额圈,我又回到了原点