LoginSignup
47
30

More than 3 years have passed since last update.

Laravel MySQL timestamp型 2038年問題に対応する

Last updated at Posted at 2020-05-07

概要

MySQLのtimestamp型は2038年問題が発生します。
MySQLのtimestamp型に起きる2038年問題

timestamp型の寿命が先に来るか、アプリの寿命が先に来るか定かではないですが後から直すことを考えると予め対策しておいた方がベターなのかなと思います。

環境

  • PHP 7.4.5
  • Laravel 7.9.2
  • MySQL 8.0.19

デフォルトの users テーブル

database/migrations/2014_10_12_000000_create_users_table.php
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });

timestamp()timestamps() の指定があります。

timestamps() の実装

vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php
    /**
     * Add nullable creation and update timestamps to the table.
     *
     * @param  int  $precision
     * @return void
     */
    public function timestamps($precision = 0)
    {
        $this->timestamp('created_at', $precision)->nullable();

        $this->timestamp('updated_at', $precision)->nullable();
    }

単純に timestamp() を呼び出しています。

問題

$ php artisan tinker

>>> factory(App\User::class)->create(['email_verified_at' => '2038-01-19 12:14:08'])

Illuminate/Database/QueryException with message 'SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: '2038-01-19 12:14:08' for column 'email_verified_at' at row 1 (SQL: insert into `users` (`name`, `email`, `email_verified_at`, `password`, `remember_token`, `updated_at`, `created_at`) values (Kelsie Renner DVM, elisha.simonis@example.org, 2038-01-19 12:14:08, $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi, Y5cwkbHJGp, 2020-05-07 00:16:31, 2020-05-07 00:16:31))'

timestamp型に 2038-01-19 12:14:08 以上の値を入れると Invalid datetime format: 1292 Incorrect datetime value エラーが発生します。

対策1 マイグレーションで dateTime 型を指定する

database/migrations/2014_10_12_000000_create_users_table.php
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->dateTime('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->dateTime('created_at')->nullable();
            $table->dateTime('updated_at')->nullable();
        });

timestampdateTime に置き換えるだけです。

対策2 Blueprint クラスに dateTimes 関数を自作して追加

timestamp型だったら $table->timestamps(); で簡単に登録できるのにな...🤔
公式に実装してもらいたいところですが、ないので仕方ありません。

app/Database/Blueprint.php を作成して dateTimes 関数を実装する。

app/Database/Blueprint.php
<?php declare(strict_types=1);

namespace App\Database;

use Illuminate\Support\Facades\DB;
use Illuminate\Database\Schema\Blueprint as BaseBlueprint;

class Blueprint extends BaseBlueprint
{
    /**
     * Add automatic creation and update dateTimes to the table.
     *
     * @param  int  $precision
     */
    public function dateTimes($precision = 0): void
    {
        $this->dateTime('created_at', $precision)->nullable();
        $this->dateTime('updated_at', $precision)->nullable();
    }
}

app/Facades/Schema.php を作成して、 Schema ファサードを作成する。

app/Facades/Schema.php
<?php declare(strict_types=1);

namespace App\Facades;

use App\Database\Blueprint;
use Illuminate\Database\Schema\Builder;
use Illuminate\Support\Facades\Schema as BaseSchema;

class Schema extends BaseSchema
{
    /**
     * Get a schema builder instance for a connection.
     *
     * @param  string|null  $name
     * @return Builder
     */
    public static function connection($name): Builder
    {
        /** @var \Illuminate\Database\Schema\Builder $builder */
        $builder = static::$app['db']->connection($name)->getSchemaBuilder();
        $builder->blueprintResolver(static function($table, $callback) {
            return new Blueprint($table, $callback);
        });
        return $builder;
    }

    /**
     * Get a schema builder instance for the default connection.
     *
     * @return Builder
     */
    protected static function getFacadeAccessor(): Builder
    {
        /** @var \Illuminate\Database\Schema\Builder $builder */
        $builder = static::$app['db']->connection()->getSchemaBuilder();
        $builder->blueprintResolver(static function($table, $callback) {
            return new Blueprint($table, $callback);
        });
        return $builder;
    }
}

config/app.php 一応、エイリアスも変更する。

config/app.php
    'aliases' => [
        // 'Schema' => Illuminate\Support\Facades\Schema::class,
        'Schema' => App\Facades\Schema::class,
    ],

マイグレーションファイルを書き換える。
名前空間とtimestampのカラムを変更します。

database/migrations/2014_10_12_000000_create_users_table.php
<?php

use App\Facades\Schema; // 変更
use App\Database\Blueprint; // 変更
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->dateTime('email_verified_at')->nullable(); // 変更
            $table->string('password');
            $table->rememberToken();
            $table->dateTimes(); // 変更
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

migrate:fresh コマンドでテーブルを作り直します。

$ php artisan migrate:fresh

テーブル構成を確認します。

> desc users;
+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | datetime        | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | datetime        | YES  |     | NULL    |                |
| updated_at        | datetime        | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------+

Typeが datetime 型になってます。
$table->dateTimes();created_atupdated_at を登録できるようになりました。
しかし、カスタマイズしたクラスの名前空間指定が必要になったので、むしろ手間が増えたような...🤔

ちなみに元(timestamp)のテーブル構成はこちらです。

> desc users;
+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------+

公式で dateTimes() 関数用意してくれないかな...🤔

参考

47
30
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
30