【laravel】リレーションの基本

こんにちは、らいか(@hideiwa1)です。

今回は、laravelでテーブル同士を関連付ける「リレーション」の使い方を解説します。
これにより、関連するテーブル同士の扱いが容易になります。

リレーションとは

複数のテーブルを扱う場合、テーブル同士が関連していることがあります。
例えば、「ユーザー」と「買った商品」、「商品」と「商品のカテゴリー」などです。

usersテーブル

idname
1山田
2加藤
3鈴木
4佐々木

itemsテーブル

idnameuser_id
1りんご2
2みかん4
3バナナ1
4ぶどう3

例えばこのような、「ユーザー」と「買った品物」のテーブルがあったとします。

リレーションを使わない場合、ユーザーの名前と買った品物を取得するには次のようになります。


  $user_id = 1;
  $user = User::find($user_id);
  $user_name = $user->name;  //名前の取得

  $item = Item::where('user_id', '=', $user_id) -> get();
  $item_name = $item->name; //品物名の取得

これをリレーションを使用すると、次のように簡潔に書くことが出来ます。


  $user_id = 1;
  $user = User::find($user_id);
  $user_name = $user->name;  //名前の取得

  $item_name = $user->item->name  //品物名の取得

リレーションの設定

では、実際にリレーションの設定をしていきましょう。

まず、テーブル同士の関係には大きく分けて「1対1」、「1対多」、「多対多」の3種類があります。

1対1

「1対1」の関係は、1つのモデルが1つのモデルと関連している状態です。
例えば、「users」テーブルの1人のユーザーが、「items」テーブルの1つの品物とのみ関連している状態です。

usersテーブル

idname
1山田
2加藤
3鈴木
4佐々木

itemsテーブル

idnameuser_id
1りんご2
2みかん4
3バナナ1
4ぶどう3

この状態では、1つのユーザーは、1つの品物とだけ関係しています。
また、1つの品物も、1つのユーザーとだけ関係があります。

この時、「users」テーブルが「親」、「items」テーブルが「子」となります。
(「子」テーブルには、「親」テーブルのIDを持つ必要があります。)

この場合のリレーションの設定は、各モデル内で次のように行います。


  app/Models/User.php  //親モデル

  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class User extends Model
  {
      /**
       * ユーザーに関連している品物の取得
       */
      public function item()
      {
          return $this->hasOne(Item::class);
      }
  }

  app/Models/Item.php  //子モデル

  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class Item extends Model
  {
      /**
       * 品物に関連しているユーザーの取得
       */
      public function user()
      {
          return $this->belongsTo(User::class);
      }
  }

「親」→「子」へのリレーションには「hasOne()
「子」→「親」へのリレーションには「belongsTo()

この設定により、モデルからリレーション先のモデルを呼び出すことが出来ます。
さらに、リレーション先のモデルのプロパティを呼び出すことも可能です。


  $item = User::find(1)->item;
  $item_name = User::find(1)->item->name;

1対多

次に「1対多」の関係です。
こちらは、1つのモデルが複数のモデルと関連している状態です。
例えば、「users」テーブルの1人のユーザーが、「items」テーブルの複数の品物と関連している状態です。

usersテーブル

idname
1山田
2加藤
3鈴木
4佐々木

itemsテーブル

idnameuser_id
1りんご1
2みかん2
3バナナ1
4ぶどう3

この状態では、1つのユーザーは、複数の品物と関係しています。
また、1つの品物は、1つのユーザーとだけ関係があります。

この時、「users」テーブルが「親」、「items」テーブルが「子」となります。
(「子」テーブルには、「親」テーブルのIDを持つ必要があります。)

この場合のリレーションの設定は、各モデル内で次のように行います。


  app/Models/User.php  //親モデル

  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class User extends Model
  {
      /**
       * ユーザーに関連している品物の取得
       */
      public function items()  // 複数形
      {
          return $this->hasMany(Item::class);
      }
  }

  app/Models/Item.php  //子モデル

  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class Item extends Model
  {
      /**
       * 品物に関連しているユーザーの取得
       */
      public function user()
      {
          return $this->belongsTo(User::class);
      }
  }

「親」→「子」へのリレーションには「hasMany()
*メソッド名は複数形

「子」→「親」へのリレーションには「belongsTo()
*メソッド名は単数形

「1対1」の時と変わって、親モデルのメソッドが「複数形」になっているのに注意です。
これは、1つのユーザーに対して複数の子モデルが関連しているためです。

そのため、各プロパティにアクセスするにはforeachなどで各配列ごとにする必要があります。


  $items = User::find(1)->items;
  foreach(User::find(1)->items as $item)
  {
      $item_name[] = $item->name;
  }

多対多

次に「多対多」の関係です。
こちらは、複数のモデルが複数のモデルと関連している状態です。
例えば、「users」テーブルの1人のユーザーが、「items」テーブルの複数の品物と関連している状態です。
さらに、「items」テーブルの1つの品物も、「users」テーブルの複数のユーザーと関連しています。

usersテーブル

idname
1山田
2加藤
3鈴木
4佐々木

item_userテーブル

idname
1りんご
2みかん
3バナナ
4ぶどう

itemsテーブル

user_iditem_id
11
14
22
21

この状態では、1つのユーザーは、複数の品物と関係しています。
また、1つの品物は、複数のユーザーと関係があります。

この場合は、2つのテーブルを結びつける「中間テーブル」が必要になります。
中間テーブルは2つのモデルを「_(アンダースコア)」でつなげます。
また、要素に「(モデル名)_id」のカラムを持たせます。

この場合のリレーションの設定は、各モデル内で次のように行います。


  app/Models/User.php 

  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class User extends Model
  {
      /**
       * ユーザーに関連している品物の取得
       */
      public function items()  // 複数形
      {
          return $this->belongsToMany(Item::class);
      }
  }

  app/Models/Item.php 

  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class Item extends Model
  {
      /**
       * 品物に関連しているユーザーの取得
       */
      public function users()  // 複数形
      {
          return $this->belongsToMany(User::class);
      }
  }

リレーションには「belongsToMany()
*メソッド名は複数形

今回も複数モデルが取得されるため、各プロパティにアクセスするにはforeachなどで各配列ごとにする必要があります。


  $items = User::find(1)->items;
  foreach(User::find(1)->items as $item)
  {
      $item_name[] = $item->name;
  }

「多対多」のリレーションの使い方については、こちらの記事も参考にしてください。

https://wp.festina-rente.com/how-to-use-belongstomany/

リレーションの応用

外部キーを変更する

リレーションは、自動的に外部キーを「親モデルの名前+_id」であると想定します。
先の「users」(親)と「items」(子)の例では、子モデルに「user_id」が存在すると想定しています。

しかし、設計上別の名前にしたい場合もあると思います。
その際には、リレーション時に外部キーの名称を指定することが出来ます。
(第2引数に指定します)

また、親モデルのid以外を関連付けたい場合も、リレーションでkeyを指定することが出来ます。
(第3引数に指定します)


  //外部キーをcustomer_keyへ変更
  return $this->hasOne(Item::class, 'customer_key');

  //usersテーブルのcustomer_idを利用
  return $this->hasOne(Item::class, 'customer_key', 'customer_id');

usersテーブル

idcustomer_idname
11001山田
22268加藤
35812鈴木
45224佐々木

itemsテーブル

idnamecustomer_key
1りんご1001
2みかん5812
3バナナ2268
4ぶどう5224

リレーションのデータを全件取得する

今までは1つのモデルについて、リレーション先のモデルを取得していました。
今回は、全部のモデルに対して取得してみたいと思います。


  $users = User::all();
  foreach($users as $user)
  {
      $items[] = $user->item;
  }

この方法でもよいのですが、「N+1問題」があるためにあまり推奨されません。
(ここでは詳しく説明しませんが、クエリの効率が良くありません。)

https://wp.festina-rente.com/n1-problem/

よりクエリの効率をよくするためには、「with」メソッドを使用します。


  $users = User::with('item')->get();
  foreach($users as $user)
  {
      $items[] = $user->item;
  }

まとめ

「1対1」の場合:
・親 「hasOne(Model::class)」
・子 「belongsTo(Model::class)」

「1対多」の場合:
・親 「hasMany(Model::class)」
・子 「belongsTo(Model::class)」

「1対多」の場合:
・親 「belongsToMany(Model::class)」
・子 「belongsToMany(Model::class)」

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です