Laravel 外部キーを設定 Cannot add foreign key


Laravel学習中に詰まったので記憶定着も兼ねてメモ。

症状

Authを使用したユーザー認証を導入し、usersテーブルがある。そこに、投稿用のpostsテーブルを用意。usersテーブルのPKであるidに対してpostsテーブルのuser_idに外部キーを設定する。

Users
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

#--省略--

public function up()
    {
        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();
        });
    }
Posts
public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->engine = 'InnoDB';
            $table->increments('id');
            $table->integer('user_id');
            $table->string('title');
            $table->string('content');
            $table->timestamps();

            $table->foreign('user_id')
                  ->references('id')
                  ->on('users')
                  ->onDelete('cascade');
        });
    }
% php artisan migrate

結果

General error: 1215 Cannot add foreign key constraint 
(SQL: alter table `posts` add constraint `posts_user_id_foreign` 
foreign key (`user_id`) references `users` (`id`) on delete cascade)
---省略---
   677▕         catch (Exception $e) {
  ➜ 678▕             throw new QueryException(
    679▕                 $query, $this->prepareBindings($bindings), $e
    680▕             );
    681▕         }
    682▕ 
---

調べてみた

外部キーが設定できないエラーなので、原因はUsersのidか、Postsの外部キーの設定のどちらかと考えるのが普通だと思う。一見外部キー制約の方には問題がなさそうだったため、まずはUsersのidから調べることにした。

そもそもid( )ってなんだ

$table->id( )とあるが、このメソッドはなんだろうか。まあ、調べなくてもidを付与する的な何かということはわかる。ただ、その中で都合が悪いためにエラーが起きている可能性はある。

id( )はどこに定義してあるのか

id( )は、\$tableに対して呼び出されている。$tableはBlueprintを引っ張っている。

Schema::create('posts', function (Blueprint $table){

Blueprintは、useで指定してあるIlluminate\Database\Schema\Blueprintにあるっぽいので、行ってみた。(ちなみに、Illuminateは、vernder/laravel/frameworkにある)

あった。

    public function id($column = 'id')
    {
        return $this->bigIncrements($column);
    }

超絶シンプル。しかし、これだとbigIncrementsが実行されていることしかわからない。
$thisって書いてあるので、ここからさらにbigIncrementsを探す。

    public function bigIncrements($column)
    {
        return $this->unsignedBigInteger($column, true);
    }

これもまたシンプル。だが、ここでもunsignedBigIntegerが実行されているだけ。
と、思いきや、よく考えると、unsignedじゃないか!!

ということで、ここで型の不一致説が濃厚になる。

外部キー制約を設けたuser_idは、単純なinteger型。
id( )をinteger型にするのは現実的ではないので、user_idにunsignedを付与してみる。

Posts
$table->integer('user_id')->unsigned();
% php artisan migrate:reset
% php artisan migrate

一度実行しているので、リセットしてからマイグレート。(ただし、自分の場合はresetでpostsテーブルがドロップされない事件が起きていたので、mysqlから直接drop table postsで行った。)

Migrating: 20xx_xx_xx_000000_create_users_table
Migrated:  20xx_xx_xx_000000_create_users_table (27.76ms)
Migrating: 20xx_xx_xx_100000_create_password_resets_table
Migrated:  20xx_xx_xx_100000_create_password_resets_table (20.34ms)
Migrating: 20xx_xx_xx_000000_create_failed_jobs_table
Migrated:  20xx_xx_xx_000000_create_failed_jobs_table (19.75ms)
Migrating: 20xx_xx_xx_084051_create_posts_table
Migrated:  20xx_xx_xx_084051_create_posts_table (27.74ms)

成功した。

最終的には、idメソッドではなくincrementsメソッドを使用したが、同じくunsignedが付与される。

Users

   public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
Posts
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->engine = 'InnoDB';
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->string('title');
            $table->string('content');
            $table->timestamps();

            // Set user ID to foreign key and automatic updating
            $table->foreign('user_id')
                  ->references('id')
                  ->on('users')
                  ->onDelete('cascade');
        });
    }

以上。