Laravelで1対1のリレーションをする方法とデータの取得方法や使い方【hasOne】
いつもご利用ありがとうございます。
この記事には広告が掲載されており、その広告費によって運営しています。
Laravel で1対1のリレーションをする方法とデータの取得方法や使い方についてまとめました。
リレーションとは?
データベースのテーブル間で関係性のあるデータのことを指します。
今回の例で言えば、
users テーブルと、user_details テーブルの2つテーブルの間で、
ユーザー ID を元に、データを紐づけるようにして使います。
つまり、user_details テーブルに user_id を保存しておけば、リレーションできるということです。
Laravel では、少しのコーディングでこれを簡単に実装することができます。
Laravel 11.x Eloquent:リレーション 1対1/1所有
検証環境
検証環境は Laravel 11 ですが、変更はされていないので Laravel 8 以降であれば動きます。
1対1のリレーション方法
テーブル設計
user_details テーブルを作成する
コマンドでマイグレーションファイルを作成
php artisan make:migration create_user_details_table
マイグレーションファイルに書く
database/migrations/2024_10_29_003907_create_user_details_table.php
Schema::create('user_details', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id'); //重要
$table->string('address');
$table->timestamps();
});
マイグレーションを実行しデータベースを変更
php artisan migrate
UserDetail モデルを作成するコマンド
php artisan make:model UserDetail
User モデルに書く
app/Models/User.php
class User extends Authenticatable
{
//*略*
public function userDetail()
{
return $this->hasOne(UserDetail::class);
}
}
このように hasOne を使うと1対1のリレーションをモデルに書くことができます。
ちなみに、UserDetail::class
の部分は、app/Models/
を基準にしています。
つまり、モデルクラスの位置が例えば、app/Models/User/UserDetail.php
の場合、User\UserDetail::class
と書きます。
コントローラーにリレーションを書く
use App\Models\User;
~~~~~~~~
public function index()
{
$users = User::with('userDetail')->get();
// dd($users);
return view('home',compact('users'));
}
userDetail
は、モデルに書いた関数名です。
with
は、Eager ロードというもので、データの取得回数を安定させ N+1 問題などの問題を解決してくれるので使っておくのが無難です。
リレーションのデータを表示する方法
blade ファイルの場合
@foreach($users as $user)
{{ $user->userDetail->address }}
@endforeach
このように、userDetail->address
とリレーション関数と取得したいカラムを繋ぐことで、表示させることができます。
なお、$users->userDetail->address
は使用できません。
必ず、$user
の個別インスタンスに対してリレーションを記述する必要があります。
React の場合
JavaScript で表示する際は、リレーションがキャメルケースではなく、スネークケースになります。
{
users.map(user => <div key={user.id}>{user.user_detail.address}</div>)
}
Vue の場合
<template>
<div v-for="user in users">
{{ user.user_detail.address }}
</div>
</template>
user_details をベースに users をリレーションする
リレーションは、ベースとなるテーブルを逆にしても関連したデータを取得することができます。
UserDetail モデルに書く
app\Models\UserDetail.php
class UserDetail extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
このように belongsTo を使うと先ほどベースのテーブルを逆にして関連したデータを取得することができます。
この hasOne と belongsTo のどちらを使うかというのは、テーブル設計の users.id = user_details.user_id の時点で決まりますので慣れで感覚的に覚えていくようになります。
最初のうちはとりあえず動けば OK で、暗記などせず次に進んで良いです。
2択なので、2つ試せば良いだけですからね。
コントローラーに書きます。
public function index()
{
$users = UserDetail::with('user')->get();
// dd($users);
return view('home',compact('users'));
}
1対1のリレーションのメリット
基本的には無駄である
1対1のリレーションは基本的に無駄です。
何故なら、users テーブルに address カラムを追加するだけで、今回頑張って実装したリレーションと同じことが実装が可能だからです。
なので、「1対1のリレーションをしなければ実装ができない」ということはあまり無いのでリレーションさせる理由がしっかり必要になってくると思います。
表示の際のパフォーマンス改善
基本的にこのデータを表示しないが特定の場合に使うようなデータをテーブルを分けて保存するパターンです。
ユーザーの一覧では表示することはないけど、ユーザー情報の登録で使うようなパターンです。
一覧などたくさんのレコードを扱って表示するページにおいては、select を使ってカラムを絞り込むより元々必要なカラムだけをそのテーブルに保存するようにした方が処理負荷や転送量を減らすことができ、レコードが増えれば増えるほどパフォーマンスに差が出ます。
データ量によるので、最初は体感しづらいため、課題を感じてから変更するプロダクトも多いんじゃないでしょうか。
セキュリティ上の対策
厳格に扱いたいデータをテーブルで分けて管理します。
なんとなくデータを返した際にそのデータが見えてしまったら問題なので一手間加えて、事故を防ぐような目的でテーブルを分けます。
管理のしやすさ
テーブルを分けた方が管理しやすい時はテーブルを分けても良いと思います。
表示をベースに管理しやすくするか、入力 をベースに管理しやすくするか、そこをしっかり決めないと意味なくて、ただテーブル分けただけ状態になります。
テーブルの容量の上限(稀)
自由テキストを入力するカラムが沢山あるテーブルを作った際にこの問題が発生しました。
稀にこういった1つのテーブルに保存できるものの上限が発生するケースがあるのでテーブルを分ける必要があります。
外部キーは必須か?
外部キーとは?
外部キーは、テーブル間のリレーションシップを定義し、データの整合性を保つ役割を果たします。
例えば、親データが削除された時にリレーションされた子データも一緒に削除されデータの整合性が保たれるというのが外部キーです。
外部キーは、Laravel ではなくデータベース側の設定で、マイグレーション時に設定するものです。
外部キーは必須ではない
色々な考え方があるのですが、自分は必須ではないと考えています。
仕様と相談し、設定が必要だと感じれば外部キーを設定した方が良いと思います。
特に理由がない場合は設定しなくて良いと思います。
データが浮くことによってバグやエラーが発生することはあり得るのですが、外部キーで対応するのか他の方法をとるべきかはその時のケースによると思います。
初学者は外部キーはつけなくて良い
外部キーをつけることによって、色々なバグが発生し、慣れていない分コードを書いて勉強するのが遅くなってしまいます。
例えばマイグレーション実行時、テストデータの作成時などです。
初学者はこの作業を初めてやることがあると思うので、その作業自 体に慣れてからチャレンジする方が良いと思います。
Laravel における外部キーの付け方
user_details
のマイグレーションファイルを以下のように変更することで実装できます。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_details', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id')->unsigned(); // unsignedを追加
$table->string('address');
$table->timestamps();
// 外部キー制約の追加
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('user_details', function (Blueprint $table) {
$table->dropForeign(['user_id']); // 外部キー制約の削除
});
Schema::dropIfExists('user_details');
}
};
まとめ
Laravel の1対1のリレーションの実装方法についてまとめてみました。
誰かの参考になれば幸いです。
関連記事
Laravel で1対多のリレーションをして保存、表示する方法【hasMany】