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

yufei       4 年, 5 月 前       2109

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_atupdated_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.
    

    大概的意思就是说 DateTimeInterfaceDateTimeDatetimeImmutable 的类型提示接口,该接口只能用于类型提示,不能被任何接口所实力化

    我要不要找个时间来解释解释 PHP 的类型提示是怎么实现的,我还得抽出时间来讲讲 PHP 的类型提示是怎么做的

    看到这里就很明了了,哈哈,其实我们接下来两条路,就是搜索 DateTimeDatetimeImmutable

    等等,不要以为最快捷的方法是是在 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 框架自定义的 datedatetime 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.