1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者】リレーションの基本と実際に疑問に思ったポイント

Posted at

はじめに

2025年4月からWebエンジニアとして働き始めて、Laravelのリレーションでつまづいた部分を整理しました。同じように悩んでいる初心者の方の参考になれば嬉しいです!

この記事の内容

  • リレーションの4つの基本パターンと使い分け
  • 実際のコード例とマイグレーション
  • 開発中によくある疑問の解決方法
  • 実践で役立つTips

リレーションとは

データベースのテーブル同士のつながりのこと

例:「ユーザーと投稿」の関係

  • ユーザー(usersテーブル)
  • 投稿(postsテーブル)

→ 「1人のユーザーは複数の投稿を持つ」= 1対多の関係

リレーションの種類

データベースのリレーションは以下の4パターンに分類される

関係 説明 Laravel 外部キー
1対1 一つのレコードが別テーブルの一つのレコードと対応 hasOne / belongsTo 片方が持つ
1対多 一つのレコードが別テーブルの複数レコードと対応 hasMany / belongsTo 「多」側が持つ
多対1 1対多の逆方向(書き方が異なるため区別) belongsTo / hasMany 「多」側が持つ
多対多 複数のレコードが複数のレコードと対応 belongsToMany 中間テーブル

** 重要なポイント:**

  • 外部キー(FK)を持つ側belongsTo()
  • 外部キーを持たれる側hasOne() または hasMany()

1対1の関係(hasOne)

例:ユーザーとプロフィール

  • 1人のユーザーは1つのプロフィールを持つ
  • 1つのプロフィールは1人のユーザーにだけ対応する

テーブル構造

-- usersテーブル
users: id, name, email

-- profilesテーブル  
profiles: id, user_id(FK), bio, avatar

マイグレーション例

// create_profiles_table.php
Schema::create('profiles', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->text('bio')->nullable();
    $table->string('avatar')->nullable();
    $table->timestamps(); 
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});

モデルの定義

// 🗂️ Userモデル(app/Models/User.php)
class User extends Authenticatable
{
    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

// 🗂️ Profileモデル(app/Models/Profile.php)
class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

使用例

$user = User::find(1);
echo $user->profile->bio; // プロフィール情報にアクセス

$profile = Profile::find(1);
echo $profile->user->name; // 逆方向のアクセス

1対多の関係(hasMany)

例:ユーザーと投稿の関係

  • 1人のユーザーは複数の投稿を持つ
  • 1つの投稿は1人のユーザーに属する

テーブル構造

-- usersテーブル
users: id, name, email

-- postsテーブル
posts: id, user_id(FK), title, content

モデルの定義

// 🗂️ Userモデル
class User extends Authenticatable
{
    public function posts()
    {
        return $this->hasMany(Post::class); // 1対多
    }
}

// 🗂️ Postモデル
class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class); // 多→1の逆方向
    }
}

使用例

$user = User::find(1);
foreach($user->posts as $post) {
    echo $post->title; // そのユーザーの投稿一覧
}

$post = Post::find(1);
echo $post->user->name; // 投稿者名

多対多の関係(belongsToMany)

例:投稿とタグ

  • 1つの投稿に複数のタグが付けられる
  • 1つのタグが複数の投稿に使われる

なぜ中間テーブルが必要なのか?

直接関係を作ろうとした場合の問題

-- これだと1つの投稿に1つのタグしか付けられない
posts: id, title, tag_id

-- これだと1つのタグに1つの投稿しか紐づかない  
tags: id, name, post_id

⭕️ 中間テーブルで解決

posts: id, title
tags: id, name  
post_tag: post_id(FK), tag_id(FK) -- 中間テーブル

マイグレーション例

// create_post_tag_table.php(中間テーブル)
Schema::create('post_tag', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('post_id');
    $table->unsignedBigInteger('tag_id');
    $table->timestamps();    
    $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
    $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});

モデルの定義

// 🗂️ Postモデル
class Post extends Model
{
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }
}

// 🗂️ Tagモデル  
class Tag extends Model
{
    public function posts()
    {
        return $this->belongsToMany(Post::class);
    }
}

使用例

$post = Post::find(1);
foreach($post->tags as $tag) {
    echo $tag->name; // その投稿のタグ一覧
}

// タグの追加・削除
$post->tags()->attach($tagId);       // タグを追加
$post->tags()->detach($tagId);       // タグを削除  
$post->tags()->sync([$tag1, $tag2]); // 指定したタグのみに同期

自分の開発中に出てきた疑問

Q1. 多対1の場面で「多」の方が主テーブルになることってないのかな?

A: あった!重要なのは「どちらが外部キー(FK)を持つか」ということ

例:投稿とカテゴリー

// 📝 投稿が外部キーを持つパターン(一般的)
// posts: id, title, category_id(FK)  
// categories: id, name

// Postモデル
public function category()
{
    return $this->belongsTo(Category::class); // 外部キーを持つ側
}

// Categoryモデル  
public function posts()
{
    return $this->hasMany(Post::class); // 外部キーを持たれる側
}

覚え方:

  • 外部キー(FK)を持つ方belongsTo()
  • 外部キーを持たれる方hasOne() / hasMany()

Q2. 中間テーブルって何?多対多だけに出てくるのか?

A: 中間テーブルは2つのテーブルの「多対多関係」を管理するための「橋渡し役」テーブル!

中間テーブルの特徴

  • 多対多のときだけ必要になる
  • 中間テーブル自体は「モデルを作らない」ことが多い(Laravelが自動で扱う)
  • 命名規則:アルファベット順の単数形をアンダースコアで結合(post_tag

なぜ必要なのか?

  • 多対多の関係を直接表現することができないため、中間テーブルで「どの投稿にどのタグが付いているか」を管理する

Q3. なんで関数名が紐付ける先のテーブル名なのか?なんで関数に書くのか?

A: これはLaravelの「Eloquent ORM」の仕組み

関数名は「関連付ける先の名前」にするのがLaravelの慣習らしい

// Postモデル内
public function user()
{
    return $this->belongsTo(User::class);
}

ここでuser()って名前にすることで

$post = Post::find(1);
echo $post->user->name; // 自然にアクセス可能!

もしも関数名を getUserInfo() とかにすると、$post->getUserInfo になってしまって、ORMの便利機能と連携しにくくなる。。。!

関数なのに自動で実行されるように見える理由
これはLaravelが「マジックメソッド」で内部処理してくれるから

$post->user // ←これを書いた時

Laravelが内部で自動的に

$post->user()      // ←関数を呼ぶ!
  ->getResults();  // ←中で自動的に実行

ってやってくれるらしい。なのでJavaScript的な感覚では「関数呼んでないのに?」ってなりますが、実際はLaravelが勝手にやってくれているだけでした。

まとめ

Laravelのリレーションは最初は複雑に感じました。。。なんで内部的に勝手に紐づくのか、みたいなことが多く癖があるなという印象でした。
ただ、リレーションに関しては以下のポイントを押さえれば理解しやすくなりました

  1. 外部キーを持つ側がbelongsTo()
  2. 関数名は関連付ける先の名前にする
  3. 多対多は中間テーブルが必要

実際にコードを書きながら覚えていくのが一番かなと思いました。
同じように悩んでいる方の参考になれば嬉しいです!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?