久しぶりのQiitaへの投稿です。
未経験からエンジニアに転職をして、2年目に突入したバックエンドエンジニアです。
DBに外部キー制約を追加をしようと思い、Laravel7から使えるエイリアスでmigrationファイルを作成していました。
ただ、テーブル名が長い時にハマってしまい、できる人には当たり前すぎる内容かもしれませんが、振り返りの意味も込めて投稿します。
開発環境
- 言語:PHP7.4.8
- フレームワーク:Laravel7.28.3
- DB:MySQL8.0.22
テーブル名が短い時
目標のテーブル構造
公式ガイドでもよく使われるような下記のようなテーブルを作り、postsテーブルのuser_idに外部キー制約をかけることを目指します。
Laravel7より前の時の記法
Laravel7より前のバージョンで外部キー制約をつけるためには、下記のようにmigrationファイルを作成をしていました。
一度user_id
のカラムを作成してから別の行で外部キー制約をつけています。
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
そんなに難しくもないですが、少しだけ面倒です。
Laravel7以降で使えるエイリアスを使用
そこでLaravel7以降では下記のようなエイリアスができ、foreignId
とconstrained()
を使えば一行でも外部キー制約をかけることができるようになりました。
短くて楽ですね。
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->timestamps();
});
テーブル名が長い時
目標のテーブル構造
問題のパターンです。下記のようなテーブルを想定して試してみます。
名前は適当に僕が好きなスポーツからとってきましたが、とりあえず2つのテーブル名が長く文字数の合計が一定数を超えると同じ現象になります。
Laravel7以降で使えるエイリアスを使用
せっかく新しい記法があるということなので、foreignId
を使った新しいエイリアスを使ってやってみます。
Schema::create('national_basketball_association_players', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('national_basketball_association_team_id')->constrained();
$table->timestamps();
});
キー名が長すぎるようで下記のようなエラーが出てしまいました。
SQLSTATE[42000]: Syntax error or access violation: 1059 Identifier name 'national_basketball_association_players_national_basketball_association_team_id_foreign' is too long (SQL: alter table `national_basketball_association_players` add constraint `national_basketball_association_players_national_basketball_association_team_id_foreign` foreign key (`national_basketball_association_team_id`) references `national_basketball_association_teams` (`id`))
通常であれば、Laravelのスキームビルダの制約命名規則に従い、自動的に外部キー制約の名前は変換されるため、外部キー制約名は下記のようになります。
(接続元テーブル名)_(外部キー名)_foreign
しかし、今回この命名規則に当てはめようとすると、national_basketball_association_players_national_basketball_association_team_id_foreign
という名前になりますが、これは文字数が長すぎるということでエラーになってしまいました。
Laravel7以降で使えるエイリアスを使用(nameでキー名変更)
同じようなことが他の制約を作る時でも起こっていたのを思い出します。
name
メソッドを使えば直せたような気がするのでname
メソッドを使って試してみました。
外部キー制約名は省略した、basketball_players_team_id_foreign
を設定します。
Schema::create('national_basketball_association_players', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('national_basketball_association_team_id')->constrained()->name('basketball_players_team_id_foreign');
$table->timestamps();
});
先程と違い、今度はエラーも起こらず、migrationファイルが無事に通りました。
念のためにdatabaseを確認してみます。
外部キー制約がついていません。。。
多分routeを作成する時に使うname
メソッドと勘違いしていたのですが、ここで結構ハマってしまいました。
migration関連でnameメソッドは用意されていなさそうでした。
エイリアスメソッドのコードを確認
改めて今回のエイリアスのforeignId
メソッドを確認してみます。
public function foreignId($column)
{
$this->columns[] = $column = new ForeignIdColumnDefinition($this, [
'type' => 'bigInteger',
'name' => $column,
'autoIncrement' => false,
'unsigned' => true,
]);
return $column;
}
引数がカラム名のみで1つしか受け付けていないので、キー名を短くして渡そうとしても無理そうです。
また、foreignId
の後に使っているconstrainedも確認してみます。
public function constrained($table = null, $column = 'id')
{
return $this->references($column)->on($table ?? Str::plural(Str::beforeLast($this->name, '_'.$column)));
}
こちらも外部キー制約名を渡す引数がなく、無理そうです。
結果:Laravel7以降で使えるエイリアスを使用(indexでキー名変更)
下記のようにindex
メソッドを使って、外部キー名を指定するとうまくいきました。
Schema::create('national_basketball_association_players', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('national_basketball_association_team_id')->constrained()->index('basketball_players_team_id_foreign');
$table->timestamps();
});
公式ガイドを見てみるとindex
メソッドは「基本的なインデックス追加」とだけ書いてありましたが、外部キー制約の名前を作成をする際にも使えるようです。
uniqueキー作るときは、第2引数にインデックスキー名指定できるのに対して、外部キー制約の場合はindex
メソッドを使わないといけなさそうなので書き方が結構違いますね。
※何か誤解していたり、他にいい方法があれば教えていただけると助かります!!!
Laravel7より前の時の記法でやる方法
もちろん元々使っていた記法を使って、foreign
メソッドの中の第2引数に外部キー制約名を指定してもうまくいきます。
このやり方は元々やったことあったのですが、ハマるくらいなら最初からこっち使っておけばよかったなと思いました、、、
Schema::create('national_basketball_association_players', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedBigInteger('national_basketball_association_team_id');
$table->foreign('national_basketball_association_team_id', 'basketball_players_team_id_foreign')->references('id')->on('national_basketball_association_teams');
$table->timestamps();
});
感想
チュートリアルで使うようなusersとかpostsとかのテーブルと違い、実務ではテーブル名が長くなるケースは命名規則によりけりだと思いますが、結構発生しそうなイメージなので意外と困ることもあるかなと思います。
また、今回少し深く調べてみて、フレームワークのコードをより深くみるようにしたのはよかったなと思いました。
それにしてもname
メソッドを使ったときに何故かmigrationファイル通るのに外部キー制約がついていないのは結構たちが悪かったです。。。