— ここでEloquent迷子は全員保護します—
どういうこと?
Eloquent を使ってると「え、ここって Active Record 的に太らせていい場所?」「pivot に追加カラム入れたら破裂したんだけど?」「syncしたらデータ消えたぞ!?」みたいな 首かしげポイント が必ず出てくる。
その都度ググって深夜に旅に出るのはしんどいので、自分が沼った順 に道標を置いておくページです。スクロールしながら “ここハマった!” と思った所でリンクを踏んでください。
アイス一本食べ終わる時間で読み終わるサイズにしてあるので、風呂上がりにでも肩の力抜いてどうぞ。
- 1. Eloquent とは? — Laravel 流 ORM の心臓部
- 1.1. Eloquent モデル:テーブル 1 枚=クラス 1 個
- 1.2. リレーション:hasOne / hasMany / belongsToMany
- 1.3. コレクション:取得後の「あと3行」をチェーンで
- 2. 立ち位置ざっくり確認 — Active Record と Data Mapper
- 2.1. ミニコード対比
- 3. リレーションは線で覚えよう
- 4. pivot の正体は関係の倉庫
- 4.1. 最低限の設計 & サンプルコード
- 4.2. 🚧 pivot テーブルを「倉庫」にする骨格
- 4.3. 🏷️ モデル側で「倉庫の荷札」を宣言する
- 4.4. 📦 倉庫に荷物を積む(追加カラム付き attach)
- 4.5. 🔍 荷札ごと取り出す(pivot プロパティ)
- 4.6. 📌 3つの持ち帰りポイント
- 5. sync 系メソッド – 関係のリセットに注意
- 5.1. よくやる事故→回避の実コード
- 6. テストで怖さを食べる
- 7. Collection でデータをちゃちゃっと後処理
- 8. Model が太ったら引っ越し
- 8.1. Faker 活用ライトガイド — “それっぽい” ダミーデータを 5 秒で錬成する
- 9. FAQ
- 10. まとめと次の一歩
Eloquent とは? — Laravel 流 ORM の心臓部
EloquentはLaravel に標準搭載されたActive Record 型 ORMモデル=テーブル行というシンプルな対応づけで、 SQL を書かずに作成・更新・検索が完結します。
ここではモデル/リレーション/コレクション の3つの観点から “Eloquent の基本形” を 30 秒でザックリ把握しておきましょう。
Eloquent モデル:テーブル 1 枚=クラス 1 個
php artisan make:model Post
生成される app/Models/Post.php
が「posts テーブル」の顔。
fillable
/ casts
/ scopeXxx
で 属性・キャスト・共通クエリ を内包できるのがスタートラインです。
リレーション:hasOne / hasMany / belongsToMany
class Post extends Model
{
public function author() { return $this->belongsTo(User::class); }
public function tags() { return $this->belongsToMany(Tag::class); }
}
リレーションメソッド = クエリビルダの別名。with('tags')
で先読み、sync()
で中間テーブルを同期——Eloquent が 人間語 API に翻訳します。
コレクション:取得後の「あと3行」をチェーンで
$activePosts = Post::latest()->get() // Eloquent→Collection
->filter(fn ($p) => $p->isPublished()) // 絞り込み
->pluck('title'); // 欲しいカラムだけ
map / filter / chunk
など 配列ライクな後処理 が標準装備。SQL に戻らずビジネスロジックの最後のひと押しを書けるのが強みです。
立ち位置ざっくり確認 — Active Record と Data Mapper
「Eloquent=Active Record」は周知の事実。でも太りすぎたらどうしよう? そこで Data Mapper の考え方が出てくる。
ミニコード対比
Active Record (Eloquent)
$user = User::create(['name' => 'Yuzu']); // モデル自身が INSERT
$user->posts()->create(['title' => 'Hi']); // リレーションもモデル発
Data Mapper (Doctrine)
$user = new User('Yuzu'); // モデルは純オブジェクト
$em->persist($user); // DB へは EntityManager が出動
$em->flush();
👉 どっち派? 踏み込んだ話はこちら → https://mikaduki.info/laravel/eloquent-active-record-vs-data-mapper/
リレーションは線で覚えよう
モデルを書く前に紙に線を引く──失敗率が 3 割減ります。こんな感じ。


あらかじめ予想しておかないと、後でアタマの疲労が三倍だよ〜!
pivot の正体は関係の倉庫
モデル同士は線路、pivot はその途中にある貨物ヤード。
線路だけ なら「A から B に届く」ことしか分からない。
でも貨物ヤードに “荷札” を貼れば──
荷札 | ピボットの追加カラム例 | 何が分かる? |
---|---|---|
granted_by | 付与者 ID | 誰が 橋を渡したか |
expires_at | 期限 | いつまで 有効か |
role | 役割/数量 | どの荷物 を積んでいるか |
最低限の設計 & サンプルコード
🚧 pivot テーブルを「倉庫」にする骨格
// migration: member_project pivot
Schema::create('member_project', function (Blueprint $t) {
$t->foreignId('project_id')->constrained()->cascadeOnDelete();
$t->foreignId('member_id')->constrained()->cascadeOnDelete();
// 荷札(メタ情報)
$t->string('role')->nullable(); // 役割ラベル
$t->foreignId('granted_by')->nullable(); // 付与者 ID
$t->timestamp('expires_at')->nullable(); // 賞味期限
$t->timestamps(); // created_at / updated_at
$t->primary(['project_id','member_id']); // 同じ荷物は 1 行だけ
});
▲ “橋渡し”に荷札カラムを足して、複合主キーで二重梱包を防止
🏷️ モデル側で「倉庫の荷札」を宣言する
// app/Models/Project.php
class Project extends Model
{
public function members()
{
return $this->belongsToMany(Member::class)
->withPivot(['role', 'granted_by', 'expires_at']) // ← 荷札を公開
->withTimestamps(); // 荷札にも時刻をスタンプ
}
}
▲ withPivot()
を忘れると Laravel は荷札を “無かったこと” にする
📦 倉庫に荷物を積む(追加カラム付き attach)
// 倉庫に積む:二重行を作らず、荷札だけ更新
$project->members()->syncWithoutDetaching([
$memberId => [
'role' => 'Lead', // 役割
'granted_by' => $adminId, // 誰が積んだ?
'expires_at' => now()->addMonth()// いつまで?
]
]);
▲ syncWithoutDetaching()
=「既存を温存+足し算」安全ルート
🔍 荷札ごと取り出す(pivot プロパティ)
// 取り出し:荷札の中身も読める
$member = $project->members()->first();
$member->pivot->role; // 'Lead'
$member->pivot->granted_by; // 付与者 ID
$member->pivot->expires_at; // 有効期限
▲ モデル同士の“線”を辿るだけで、倉庫のメタ情報も一緒に到着
📌 3つの持ち帰りポイント
- 荷札=追加カラム は
withPivot()
で“存在を宣言”する syncWithoutDetaching()
で二重 INSERT を未然にブロック- 複合主キー で物理的な重複をシャットアウト
これで“線路だけじゃ運べない情報”を pivot 倉庫に安全ストックできます。
👉 設計テンプレと実戦例はこちら → withPivot で追加カラム守る話
sync 系メソッド – 関係のリセットに注意
「sync は仲直りというより関係をぜんぶリセットして作り直す荒業。
フォームから空配列が飛んできた瞬間に
bootで意図しないdetachが走り、ロールが蒸発──そんな地獄を何度も見てきました。
ここでは、消えないし重ならないを守る。 最短 3 パターンだけを貼っておきます。
よくやる事故→回避の実コード
// 💥 NG: チェックボックス空配列で全ロール蒸発
$user->roles()->sync($request->input('roles', []));
// 😊 OK: 空配列を門前払い(バリデーション+トランザクション)
$ids = $request->validate(['roles' => 'required|array|min:1'])['roles'];
DB::transaction(fn () => $user->roles()->sync($ids));
// 🙆♂️ 足し算だけしたいとき(既存は残す)
$user->roles()->syncWithoutDetaching([
$roleId => ['granted_by' => Auth::id()],
]);
👉 5 大リスクと詳しい解説はこちら → sync 系完全攻略
テストで怖さを食べる
「動くから OK」でデプロイすると 3 ヶ月後に墓場行き👻 二重行防止 と pivot カラム検証 だけは書こう。
it('syncWithoutDetaching は二重行を作らない', function () {
$project = Project::factory()->create();
$member = Member::factory()->create();
$project->members()->syncWithoutDetaching([$member->id => ['role' => 'Lead']]);
$project->members()->syncWithoutDetaching([$member->id => ['role' => 'Lead']]);
expect(DB::table('member_project')->count())->toBe(1);
});
👉 フルセットのサンプルは → 多対多リレーション運用大全
Collection でデータをちゃちゃっと後処理
配列操作の感覚で仕分けるのがCollectionの醍醐味。
取得後に「VIP だけ抽出→メールアドレスだけ欲しい」──そんなお願いを、 if 文も foreach も無しで片づけます。
$vipMails = User::with('roles')->get()
->filter(fn ($u) => $u->isVip())
->pluck('email');
👉️ Collectionの実践例で map()
/ chunk()
/ pipe()
など “あと1手” を増やそう。→ Laravel Collection 実践パターン 7 選(Pest テスト付き)
Model が太ったら引っ越し
モデルがクエリも計算も通知もぜんぶ抱え込み始める状態になった責務分担を考えましょう。
たとえば「DB操作+計算+通知+エラー処理+テスト用のモック」が芋づる式に増え、 たった数メソッドの追加のつもりでもでも行数が雪だるま式に膨らんで一気に“読み切れない1,000 行ファイルへ化けかねません。
深掘りは記事は下記
👉️ 抽象講座
Faker 活用ライトガイド — “それっぽい” ダミーデータを 5 秒で錬成する
「Seeder は書けるけど名前が全部 John Smith …?」
Faker で ロケール切替/Custom Provider/unique()
× seed()
をサッと仕込めば、
開発 DB が一瞬で “世界旅行 & 業界特化” のリア充データベースに早変わり。
デモもテストも 映え るサンプルが秒で量産できるので、
ダミーデータがダミーっぽすぎる 悩みはここで成仏させよう。
👉️ Faker活用ライトガイド – “それっぽい” ダミーデータを 5 秒で錬成する
FAQ
-
Eloquentで「N+1問題」って何?
-
リレーションを定義せず、ループ内で都度DBアクセスが発生し爆速で遅くなる現象。
→with()
やload()
で事前取得すると吉。
-
withPivot() 書かないと何が壊れる?
-
追加カラムが 取得も保存もスルー。null で帰ってきて静かに事故る。必ず宣言を。
-
attach() と sync() の決定的な違いは?
-
attach() は単発 INSERT、重複気にしない。sync() は「渡した ID 以外 DELETE→INSERT」のフル同期。誤爆率も桁違い。
-
syncWithoutDetaching() で 更新 はどうなる?
-
ID が既にあれば UPDATE、無ければ INSERT。削除はしない足し算専用メソッド。
-
複合主キー入れると何が嬉しい?
-
(user_id, role_id) の二重行を 物理的にブロック。コードの凡ミスでも DB が守ってくれる最終防壁。
-
モデルが太り始めた目安は?
-
モデルの、属性、関係、範囲、固有ルール以外のものが書かれた出した。または、これらが複雑化仕出した時。
-
Active Record と Data Mapper、結局どっち?
-
納期優先&単純 CRUD → Active Record(Eloquent)で爆速。長寿&複数 DB → Data Mapper(Doctrine)で後悔しない。
まとめと次の一歩
- pivot = 関係の倉庫。
withPivot()
と複合 PK で守る。 sync
とsyncWithoutDetaching()
は目的で使い分け。- テストは未来の自分へのラブレター。最低 2 本でいいから書く。
気になる所はリンク先で理解を深めよう!
コメントを残す