【Laravel,MySQL】 hasOneメソッドで関連付けられるテーブルの項目名はどのように決まっているのか

#概要
本記事は、[PHPフレームワークLaravel入門 第2版](https://www.amazon.co.jp/PHP%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AFLaravel%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-%E6%8E%8C%E7%94%B0-%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798060992/ref=pd_lpo_1?pd_rd_i=4798060992&psc=1)で学習している中の疑問・つまづきの備忘録です。
今回はhasOneメソッドで関連付けられるテーブルの項目名の決まり方について、疑問を調べたのでまとめました。

#環境
- Docker
- Laravel Sail
- MySQL8.0

#今回の疑問
なぜ以下のようにhasOneメソッドを使用することで、Personモデル(peopleテーブル)のidとBoardモデルクラス(boardsテーブル)のperson_idが結びつけられるのか疑問に思いました。
このメソッドにはidやperson_idという情報は渡していません。

```Person.php
public function board()
{
    return $this->hasOne('App\Models\Board');
}
```
ちなみに、公式ドキュメントには以下のような記載があります。
idとxxx_idが関連付けられることは分かりますが、それがなぜかは不明なので調べてみました。

>Eloquentは、親モデル名に基づきリレーションの外部キーを決定します。この場合、Phoneモデルは自動的にuser_id外部キーを持っているとみなします。
>[Laravel 8.x Eloquent:リレーション](https://readouble.com/laravel/8.x/ja/eloquent-relationships.html)


##テーブルの情報
###peopleテーブル
```
mysql> SHOW CREATE TABLE people;

| Table  | Create Table                                                                                                                                                                                                                                                                                                                                                                                                          |
+--------+----------------------------------------------------+
| people | CREATE TABLE `people` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,   ///////////////////ここと
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, 
  `mail` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `age` int NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=72824 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
```
###boardsテーブル

```
mysql> SHOW CREATE TABLE boards;

| Table  | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                |
+--------+----------------------------------------------------+
| boards | CREATE TABLE `boards` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `person_id` int NOT NULL,   /////////////////////////////ここが関連付けられている
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `message` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
```

#hasOneメソッドの動きを追ってみた
Person.phpで使用しているhasOneメソッドの呼び出し先を確認していきます。
hasOneメソッドは、HasRelationships.phpで以下のように宣言されています。

```HasRelationships.php
    ///一部抜粋
    public function hasOne($related, $foreignKey = null, $localKey = null)
    {
        $instance = $this->newRelatedInstance($related);

        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $localKey = $localKey ?: $this->getKeyName();

        return $this->newHasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
    }
```

ここで注目するのは引数です。

- `$related`・・・・どのモデルと関連付けるかを指定。今回は'App\Models\Board'を渡している。
- `$foreignKey`・・・・`$related`で指定したモデルのどのカラムと`$localKey`を関連付けるかを指定。デフォルトではnull。
- `$localKey`・・・・`$foreignKey`と関連付けられる、このモデルのカラムを指定。デフォルトではnull。

今回、hasOneメソッドには`$related`しか引数を渡していないので、`$this->getForeignKey()`の戻り値が`$foreignKey`に格納されます。
getoForeignKeyメソッドの宣言は以下のようになっています。

```Model.php
    ///一部抜粋
    public function getForeignKey()
    {
        return Str::snake(class_basename($this)).'_'.$this->getKeyName();
    }
```
Str::snakeは、引数の値をスネークケースにして返します。
>Str::snakeメソッドは文字列をスネークケース（snake_case）に変換します。

ここでの`$this`はPersonのインスタンスなのでclass_basename関数はPersonという文字列を返します。

>class_basename関数は、指定クラスの名前から名前空間を取り除いて返します。
>[Laravel 8.x ヘルパ](https://readouble.com/laravel/8.x/ja/helpers.html#method-class-basename)


では、getKeyNameメソッドはどうでしょうか。

```Model.php 
    ///一部抜粋
    public function getKeyName()
    {
        return $this->primaryKey;
    }
```
ここでも`$this`はPersonのインスタンスなので、Personモデル(peopleテーブル)のプライマリキーであるidを返しています。

これらのことより、`$foreignKey`の値は「person_id」となります。

次に`$localKey`ですが、これはPersonモデル(peopleテーブル)のプライマリキーなので「id」となります。

したがって、Personモデルのidと、Boardモデルのperson_idが関連付けられます。

#まとめ
hasOneメソッドで関連付けられるテーブルの項目名は、hasOneメソッドを遡った先にあるgetForeignKeyメソッドおよびgetKeyNameメソッドによって決まっている。

基本的には「id」と「モデル名_id」が関連付けられる。

#参考文献

>[PHPフレームワークLaravel入門 第2版](https://www.amazon.co.jp/PHP%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AFLaravel%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-%E6%8E%8C%E7%94%B0-%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798060992/ref=pd_lpo_1?pd_rd_i=4798060992&psc=1)
>[Laravel 8.x Eloquent:リレーション](https://readouble.com/laravel/8.x/ja/eloquent-relationships.html)
>[Laravel 8.x ヘルパ](https://readouble.com/laravel/8.x/ja/helpers.html#method-class-basename)