こんにちは、らいか(@hideiwa1)です。
今回は、laravelでテーブル同士を関連付ける「リレーション」の使い方を解説します。
これにより、関連するテーブル同士の扱いが容易になります。
リレーションとは
複数のテーブルを扱う場合、テーブル同士が関連していることがあります。
例えば、「ユーザー」と「買った商品」、「商品」と「商品のカテゴリー」などです。
usersテーブル
| id | name |
|---|---|
| 1 | 山田 |
| 2 | 加藤 |
| 3 | 鈴木 |
| 4 | 佐々木 |
itemsテーブル
| id | name | user_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テーブル
| id | name |
|---|---|
| 1 | 山田 |
| 2 | 加藤 |
| 3 | 鈴木 |
| 4 | 佐々木 |
itemsテーブル
| id | name | user_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テーブル
| id | name |
|---|---|
| 1 | 山田 |
| 2 | 加藤 |
| 3 | 鈴木 |
| 4 | 佐々木 |
itemsテーブル
| id | name | user_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テーブル
| id | name |
|---|---|
| 1 | 山田 |
| 2 | 加藤 |
| 3 | 鈴木 |
| 4 | 佐々木 |
item_userテーブル
| id | name |
|---|---|
| 1 | りんご |
| 2 | みかん |
| 3 | バナナ |
| 4 | ぶどう |
itemsテーブル
| user_id | item_id |
|---|---|
| 1 | 1 |
| 1 | 4 |
| 2 | 2 |
| 2 | 1 |
この状態では、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テーブル
| id | customer_id | name |
|---|---|---|
| 1 | 1001 | 山田 |
| 2 | 2268 | 加藤 |
| 3 | 5812 | 鈴木 |
| 4 | 5224 | 佐々木 |
itemsテーブル
| id | name | customer_key |
|---|---|---|
| 1 | りんご | 1001 |
| 2 | みかん | 5812 |
| 3 | バナナ | 2268 |
| 4 | ぶどう | 5224 |
リレーションのデータを全件取得する
今までは1つのモデルについて、リレーション先のモデルを取得していました。
今回は、全部のモデルに対して取得してみたいと思います。
$users = User::all();
foreach($users as $user)
{
$items[] = $user->item;
}
この方法でもよいのですが、「N+1問題」があるためにあまり推奨されません。
(ここでは詳しく説明しませんが、クエリの効率が良くありません。)
よりクエリの効率をよくするためには、「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)」
コメントを残す