概要
MySQLのtimestamp型は2038年問題が発生します。
MySQLのtimestamp型に起きる2038年問題
timestamp型の寿命が先に来るか、アプリの寿命が先に来るか定かではないですが後から直すことを考えると予め対策しておいた方がベターなのかなと思います。
環境
- PHP 7.4.5
- Laravel 7.9.2
- MySQL 8.0.19
デフォルトの users テーブル
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() の実装
/**
* 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 型を指定する
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();
});
timestamp
を dateTime
に置き換えるだけです。
対策2 Blueprint クラスに dateTimes 関数を自作して追加
timestamp型だったら $table->timestamps();
で簡単に登録できるのにな...🤔
公式に実装してもらいたいところですが、ないので仕方ありません。
app/Database/Blueprint.php
を作成して dateTimes
関数を実装する。
<?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
ファサードを作成する。
<?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
一応、エイリアスも変更する。
'aliases' => [
// 'Schema' => Illuminate\Support\Facades\Schema::class,
'Schema' => App\Facades\Schema::class,
],
マイグレーションファイルを書き換える。
名前空間とtimestampのカラムを変更します。
<?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_at
と updated_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()
関数用意してくれないかな...🤔