【Laravel】テーブル名が長い時に、Laravel7から導入された外部キー制約をつけるマイグレーションエイリアスを使う方法

久しぶりのQiitaへの投稿です。

未経験からエンジニアに転職をして、2年目に突入したバックエンドエンジニアです。
DBに外部キー制約を追加をしようと思い、Laravel7から使えるエイリアスでmigrationファイルを作成していました。
ただ、テーブル名が長い時にハマってしまい、できる人には当たり前すぎる内容かもしれませんが、振り返りの意味も込めて投稿します。

# 開発環境

- 言語：PHP7.4.8
- フレームワーク：Laravel7.28.3
- DB：MySQL8.0.22

# テーブル名が短い時

## 目標のテーブル構造
公式ガイドでもよく使われるような下記のようなテーブルを作り、postsテーブルのuser_idに外部キー制約をかけることを目指します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/aa911825-bbc7-d543-a9f9-c1dd7daa96a6.png)

## 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');
        });
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/cd1f81d1-8da0-a89a-15fe-ca97c93b479a.png)

そんなに難しくもないですが、少しだけ面倒です。

## Laravel7以降で使えるエイリアスを使用
そこでLaravel７以降では下記のようなエイリアスができ、`foreignId`と`constrained()`を使えば一行でも外部キー制約をかけることができるようになりました。
短くて楽ですね。

```
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained();
            $table->timestamps();
        });
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/64cef042-f94b-1028-830a-e1a8648b621c.png)

# テーブル名が長い時
## 目標のテーブル構造
問題のパターンです。下記のようなテーブルを想定して試してみます。
名前は適当に僕が好きなスポーツからとってきましたが、とりあえず2つのテーブル名が長く文字数の合計が一定数を超えると同じ現象になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/ed6d7ddb-d7de-01f4-23d0-ec782514cdbf.png)


## 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を確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/095e9e19-9417-66da-32f3-3284bf12f0a1.png)

外部キー制約がついていません。。。
多分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;
    }
```
引数がカラム名のみで１つしか受け付けていないので、キー名を短くして渡そうとしても無理そうです。

また、`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`メソッドを使わないといけなさそうなので書き方が結構違いますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/09027d02-5480-0a90-f330-b3fa13869672.png)

https://readouble.com/laravel/7.x/ja/migrations.html

※何か誤解していたり、他にいい方法があれば教えていただけると助かります！！！


## 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();
        });
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451174/81abd48f-57a1-d41f-4019-4bd7dd1386ac.png)


# 感想

チュートリアルで使うようなusersとかpostsとかのテーブルと違い、実務ではテーブル名が長くなるケースは命名規則によりけりだと思いますが、結構発生しそうなイメージなので意外と困ることもあるかなと思います。

また、今回少し深く調べてみて、フレームワークのコードをより深くみるようにしたのはよかったなと思いました。

それにしても`name`メソッドを使ったときに何故かmigrationファイル通るのに外部キー制約がついていないのは結構たちが悪かったです。。。