Laravel 解决 Carbon 或数据库 created_at | update_at 的时间格式问题?我是如何巧妙的找到的

yufei       6 年, 2 月 前       3192

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');
    }

    // .... 此处省略发牢骚的话
}

简单来说,就是两步走

  1. 引用相关类 DateTimeInterface 类,这个类是 PHP5.8 之后自带的,就是为了解决一些遗留问题留下的

  2. 重写 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

  1. 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 找到突破口了。

  2. 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 和 datetime 2. 自定义的 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 {};
    
  3. 不要太过兴奋了,大家,因为还有最后的证明,我们重新梳理下上面的代码,上面的代码是说在三种模式下都会调用 Model 类的

    $this->serializeDate($attributes[$key]);
    

    方法,那么我们就再来查查这个serializeDate这个方法。

    一番查找,其实不用查找了,自动跳转吧,还是那个 HasAttributes 里面

    protected function serializeDate(DateTimeInterface $date)
    {
        return Carbon::instance($date)->toJSON();
    }
    
  4. 验证啦,真的验证啦。到这里,其实都快闭环了,可是我们还得继续,还得继续。 我们再来查查 toJSON() 方法。这次还是老惯例,直接查 vendor 目录得到下面的代码。

    不要任何吹风之力,你就能看到

    public function toJSON()
    {
        return $this->toISOString();
    }
    
  5. 继续顺藤摸瓜看 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'

    画了一额圈,我又回到了原点

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

♥   简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.