

🤔 ทำไม Relationships ถึงสำคัญ#
ในการเขียนโปรแกรมด้วย Laravel เราสามารถดึงข้อมูลจากฐานข้อมูลได้หลากหลายวิธีมาก ไม่ว่าจะเป็นการเรียกผ่าน Facade DB ตรง ๆ หรือการใช้ Model ช่วย Query ก็ตาม แต่อีก 1 วิธีที่เราสามารถใช้เรียกข้อมูลได้เหมือนกันก็คือการดึงผ่าน Eloquent Relationships นั่นเอง
การดึงข้อมูลด้วย Eloquent Relationships นั้นจะเป็นการดึงข้อมูลที่มีความสัมพันธ์ซึ่งกันและกัน โดยมีข้อดีคือ เราไม่ต้องคอยเขียนโค้ดเพื่อบริหารจัดการความสัมพันธ์ทุกครั้งที่เรียกใช้ ขอเพียงเราสร้างและประกาศความสัมพันธ์ไว้ก่อน เวลาเรียกใช้ก็เพียงแค่ดึงผ่านตัว Model เท่านั้น ทำให้การเขียนโค้ดของเรามีประสิทธิภาพมากยิ่งขึ้น นอกจากนี้ การดึงข้อมูลในรูปแบบของ Relationships ยังมีส่วนช่วยในการมองภาพของระบบและทำความเข้าใจกับ Business Logic ของ Model นั้น ๆ ได้ดียิ่งขึ้นอีกด้วย
💻 พื้นฐาน Eloquent Relationships#
การจะดีไซน์ Eloquent Relationships นอกจากจะต้องรู้พื้นฐานของภาษา PHP และ Laravel แล้ว ยังต้องเข้าใจความสัมพันธ์ในเชิงของฐานข้อมูลควบคู่กันไปด้วย เพื่อให้การดีไซน์ Relationships เกิดประสิทธิภาพสูงสุดและมีความถูกต้องในเชิง Logic ก่อนลงรายละเอียด ขอปูพื้นฐานคำศัพท์บางคำที่จำเป็นต่อการทำความเข้าใจไว้ก่อน
- Pivot Table - คือตารางกลาง ที่ใช้สำหรับเชื่อมความสัมพันธ์แบบ Many-to-Many โดยจะเก็บ foreign key ของทั้ง 2 Model ไว้ใช้สำหรับดึงข้อมูล
- Eager Loading - คือการดึงข้อมูลจากความสัมพันธ์พร้อมกันล่วงหน้า เพื่อป้องกันการ Query ที่เยอะเกินจำเป็น นอกจากนี้ยังช่วยป้องกันปัญหา N+1 อีกด้วย (ไว้ในโอกาสหน้าจะมาเล่าเกี่ยวกับปัญหา N+1 อีกที)
ตัวอย่างเช่น
User::with('posts')->get();คือการ eager load ตั้งแต่ตอน Query ส่วน$user->load('roles');คือ lazy eager load ที่โหลดทีหลังจากดึง Model มาแล้ว ถ้าไม่ใช้withแล้ววน loop เรียก$user->postsทีละตัว จะเกิดปัญหา N+1 ทันที - Polymorphic - หรือ Polymorphism หมายถึง การมีหลายรูปแบบ ซึ่งในกรณีของ Eloquent Relationships นี้คำว่า Polymorphic มีความหมายเฉพาะตัวคือ
การที่ Model หนึ่งสามารถมีความสัมพันธ์แบบ One-to-Many กับหลาย Model ได้ (หลายรูปแบบ) ผ่านคู่คอลัมน์
*_id+*_typeชุดเดียว โดยไม่ต้องสร้าง foreign key แยกสำหรับแต่ละ Model ปลายทาง
ในความเป็นจริงแล้วคำศัพท์เหล่านี้มีรายละเอียดเฉพาะตัวอีกมาก แต่ในที่นี้ขอให้เข้าใจพื้นฐานโดยคร่าวก่อน ก็เพียงพอที่จะดีไซน์ความสัมพันธ์ได้อย่างมีประสิทธิภาพแล้ว
📝 ประเภทของ Eloquent Relationships ใน Laravel#
ใน Laravel เราจะพบกับความสัมพันธ์ และ Eloquent Relationships ของ Model ดังต่อไปนี้
One-to-One (hasOne / belongsTo)#
เป็นความสัมพันธ์แบบที่ Model หนึ่งในตาราง A เชื่อมโยงกับ Model เดียวในตาราง B
One-to-Many (hasMany / belongsTo)#
เป็นความสัมพันธ์แบบที่ Model หนึ่งในตาราง A เชื่อมโยงกับหลาย Model (หนึ่ง Model ขึ้นไป) ในตาราง B
Many-to-Many (belongsToMany)#
เป็นความสัมพันธ์แบบที่ Model ในตาราง A สามารถเชื่อมโยงกับ Model ในตาราง B ได้มากกว่า 1 Model โดยผ่านตารางกลางที่เป็น Pivot Table
Has One Through (hasOneThrough)#
เป็นความสัมพันธ์แบบที่ Model ในตาราง A สามารถเชื่อมโยงกับ Model ในตาราง C ได้ 1 Model ผ่านตัวกลางที่เป็นตาราง B
Has Many Through (hasManyThrough)#
เป็นความสัมพันธ์แบบที่ Model ในตาราง A สามารถเชื่อมโยงกับ Model ในตาราง C ได้มากกว่า 1 Model ผ่านตัวกลางที่เป็นตาราง B
Polymorphic One-to-One (morphOne)#
เป็นความสัมพันธ์แบบที่ Model หนึ่งในตาราง A เชื่อมโยงกับ Model เดียวในตาราง B แบบ Polymorphic
Polymorphic One-to-Many (morphMany)#
เป็นความสัมพันธ์แบบที่ Model หนึ่งในตาราง A เชื่อมโยงกับ Model ในตาราง B มากกว่า 1 Model แบบ Polymorphic
Polymorphic Many-to-Many (morphToMany)#
เป็นความสัมพันธ์แบบที่ Model ในตาราง A สามารถเชื่อมโยงกับ Model ในตาราง B ได้มากกว่า 1 Model แบบ Polymorphic
จะสังเกตว่าจริง ๆ แล้วรูปแบบความสัมพันธ์ของ Eloquent Relationships จะเหมือนกับความสัมพันธ์ของฐานข้อมูลเลย เพียงแค่ตัว Laravel นั้นจะเพิ่มฟังก์ชันการใช้งานให้เรา เพื่อให้เราเรียกใช้งานได้สะดวกขึ้นเท่านั้นเอง
👩💻 ตัวอย่างฐานข้อมูล เตรียม Migration และ Model ให้พร้อม#
พอเห็นภาพแล้วว่า Eloquent Relationships มีประโยชน์อย่างไร ก็ได้เวลาลองดูโค้ดจริง ๆ กันเลย
ก่อนอื่นให้เตรียมฐานข้อมูลสำหรับใช้ในการ Query ให้พร้อมโดยการพิมพ์คำสั่งดังนี้
php artisan make:migration create_countries_table
php artisan make:migration create_users_table
php artisan make:migration create_profiles_table
php artisan make:migration create_roles_table
php artisan make:migration create_role_user_table
php artisan make:migration create_posts_table
php artisan make:migration create_comments_table
php artisan make:migration create_videos_table
php artisan make:migration create_photos_table
php artisan make:migration create_tags_table
php artisan make:migration create_taggables_tablebashหลังจากสร้างไฟล์ Migration แล้วให้จัดการ fields ของแต่ละตารางดังนี้
create_countries_table
public function up(): void {
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('iso2', 2)->unique();
$table->timestamps();
});
}phpcreate_users_table
public function up(): void {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('country_id')->nullable()->constrained()->nullOnDelete();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
$table->index('country_id');
});
}phpcreate_profiles_table
public function up(): void {
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unique()->constrained()->cascadeOnDelete();
$table->text('bio')->nullable();
$table->string('phone')->nullable();
$table->timestamps();
});
}phpcreate_roles_table
public function up(): void {
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('label')->nullable();
$table->timestamps();
});
}phpcreate_role_user_table
public function up(): void {
Schema::create('role_user', function (Blueprint $table) {
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('assigned_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
$table->primary(['role_id', 'user_id']);
});
}phpcreate_posts_table
public function up(): void {
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->text('body')->nullable();
$table->enum('status', ['draft','published'])->default('draft');
$table->timestamps();
$table->index('user_id');
$table->index('status');
});
}phpcreate_comments_table
public function up(): void {
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->text('content');
$table->timestamps();
$table->index(['post_id', 'user_id']);
});
}phpcreate_videos_table
public function up(): void {
Schema::create('videos', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->string('title');
$table->string('url');
$table->timestamps();
$table->index('user_id');
});
}phpcreate_photos_table
public function up(): void {
Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->morphs('imageable'); // imageable_type, imageable_id (indexed)
$table->string('path');
$table->string('alt')->nullable();
$table->timestamps();
});
}phpcreate_tags_table
public function up(): void {
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}phpcreate_taggables_table
public function up(): void {
Schema::create('taggables', function (Blueprint $table) {
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable'); // taggable_type, taggable_id
$table->foreignId('added_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
$table->index(['tag_id', 'taggable_type', 'taggable_id'], 'taggables_full_index');
});
}phpจากนั้นสั่ง
php artisan migratebashเป็นอันเรียบร้อยสำหรับการเตรียมฐานข้อมูล จากนั้นให้เตรียม Model ต่อ โดยการพิมพ์คำสั่งดังนี้
php artisan make:model Country
php artisan make:model User
php artisan make:model Profile
php artisan make:model Role
php artisan make:model Post
php artisan make:model Comment
php artisan make:model Video
php artisan make:model Photo
php artisan make:model Tagbashจากนั้นแก้ไขไฟล์ของ Model แต่ละไฟล์ดังนี้
Country.php
class Country extends Model
{
protected $fillable = ['name', 'iso2'];
public function users() {
return $this->hasMany(User::class);
}
public function posts() {
return $this->hasManyThrough(Post::class, User::class);
}
public function latestPost() {
return $this->hasManyThrough(Post::class, User::class)->one()->latestOfMany();
}
}phpUser.php
class User extends Authenticatable
{
use HasFactory, Notifiable;
public function country() { return $this->belongsTo(Country::class); }
public function profile() { return $this->hasOne(Profile::class); }
public function posts() { return $this->hasMany(Post::class); }
public function videos() { return $this->hasMany(Video::class); }
public function roles() { return $this->belongsToMany(Role::class)->withTimestamps()->withPivot(['assigned_by', 'expires_at']); }
public function photo() { return $this->morphOne(Photo::class, 'imageable'); }
}phpProfile.php
class Profile extends Model
{
protected $fillable = ['user_id', 'bio', 'phone'];
public function user() {
return $this->belongsTo(User::class);
}
}phpRole.php
class Role extends Model
{
protected $fillable = ['name','label'];
public function users() {
return $this->belongsToMany(User::class)->withTimestamps();
}
}phpPost.php
class Post extends Model
{
protected $fillable = ['user_id','title','body','status'];
public function user() { return $this->belongsTo(User::class); }
public function comments() { return $this->hasMany(Comment::class); }
public function photos() { return $this->morphMany(Photo::class, 'imageable'); }
public function tags() { return $this->morphToMany(Tag::class, 'taggable')->withPivot(['added_by']); }
}phpComment.php
class Comment extends Model
{
protected $fillable = ['post_id','user_id','content'];
public function post() { return $this->belongsTo(Post::class); }
public function user() { return $this->belongsTo(User::class); }
}phpVideo.php
class Video extends Model
{
protected $fillable = ['user_id','title','url'];
public function user() { return $this->belongsTo(User::class); }
public function tags() { return $this->morphToMany(Tag::class, 'taggable')->withPivot(['added_by']); }
}phpPhoto.php
class Photo extends Model
{
protected $fillable = ['path','alt'];
public function imageable() {
return $this->morphTo();
}
}phpTag.php
class Tag extends Model
{
protected $fillable = ['name'];
public function posts() { return $this->morphedByMany(Post::class, 'taggable'); }
public function videos() { return $this->morphedByMany(Video::class, 'taggable'); }
}phpหลังจากเพิ่ม Model เสร็จเรียบร้อย มาลองดูตัวอย่างการดึงข้อมูลแบบต่าง ๆ ได้เลย
🛠️ ตัวอย่างการดึงและอัปเดตข้อมูลแบบต่าง ๆ ผ่าน Eloquent Relationships#
หลังจากที่เตรียมไฟล์ Model เสร็จเรียบร้อย ก็ได้เวลาลองใช้งาน Eloquent Relationships โดยจะแบ่งเป็นประเภทดังนี้
ตัวอย่าง One-to-One (hasOne / belongsTo)#
ตัวอย่างการดึงข้อมูลแบบ One-to-One ผ่านฟังก์ชัน hasOne จะเป็นการเรียกข้อมูล Profile ของ User คนนั้น ๆ
โดยที่หากเราต้องการแก้ไขข้อมูล Profile ของ User เราสามารถทำได้ง่าย ๆ ดังนี้
// Read
$profile = $user->profile;
$owner = $profile->user;
// Create
$user->profile()->create([
'bio' => 'I love Laravel',
'phone' => '0812345678',
]);
// Update
$user->profile->update(['phone' => '0888888888']);phpตัวอย่าง One-to-Many (hasMany / belongsTo)#
สำหรับในตัวอย่างนี้ เราจะมาดูที่ความสัมพันธ์ของ User และ Post โดยที่ User 1 คน สามารถเขียนได้หลาย Post
สามารถใช้ Eloquent Relationships เรียกข้อมูลได้ดังนี้
// Read
$posts = $user->posts;
$owner = $post->user;
// Create
$user->posts()->createMany([
['title' => 'A', 'status' => 'draft'],
['title' => 'B', 'status' => 'published'],
]);
// Update
$user->posts()->where('status', 'draft')->update(['status' => 'published']);phpตัวอย่าง Many-to-Many (belongsToMany)#
ตัวอย่าง Many-to-Many เราจะใช้ความสัมพันธ์ระหว่าง User และ Role โดยการดึงข้อมูลผ่านตาราง Pivot ชื่อ role_user โดยสามารถดึงข้อมูลได้โดยใช้ฟังก์ชันดังนี้
// Read
$roles = $user->roles;
// Create
$user->roles()->attach($roleId, [
'assigned_by' => auth()->id(),
'expires_at' => now()->addMonth(),
]);
// Update
$user->roles()->updateExistingPivot($roleId, [
'expires_at' => now()->addMonths(3),
]);phpตัวอย่าง Has One/Many Through (hasOneThrough / hasManyThrough)#
ในตัวอย่างนี้ เราจะลองดึงข้อมูลของ Post ภายใต้ Country ผ่านทาง Model ตัวกลางคือ User ดังตัวอย่างต่อไปนี้
// Read
$posts = $country->posts;
$latest = $country->latestPost;
// Create
$user = $country->users()->firstOrFail();
$user->posts()->create(['title' => 'From Country', 'status' => 'published']);
// Update
$country->posts()->where('status', 'draft')->update(['status' => 'published']);phpตัวอย่าง Polymorphic One-to-One / One-to-Many (morphOne / morphMany)#
การดึงข้อมูลแบบ Polymorphic ในตัวอย่างนี้ จะใช้ Model Photo โดยที่ ทั้ง User และ Post สามารถมี Photo ได้ทั้งคู่
แต่เราจะใช้คุณสมบัติของ Polymorphic ในการจัดการ Photo แยกออกจากกัน โดยที่ User จะมี 1 Photo และ Post มีมากกว่า 1 Photo
// Read
$photo = $user->photo;
$photos = $post->photos;
// Create
$user->photo()->create([
'path' => 'avatars/u1.jpg',
'alt' => 'User avatar',
]);
$post->photos()->createMany([
['path' => 'posts/1/cover.jpg', 'alt' => 'Cover'],
['path' => 'posts/1/detail.jpg', 'alt' => 'Detail'],
]);
// Update
$photo->update(['alt' => 'Main Cover']);
$post->photos()->whereNull('alt')->update(['alt' => 'N/A']);phpตัวอย่าง Polymorphic Many-to-Many (morphToMany / morphedByMany)#
สำหรับตัวอย่างสุดท้ายจะเป็นการใช้ Polymorphic แบบ Many-to-Many กับตาราง Tag โดยที่ทั้ง Post และ Video สามารถมีได้หลาย Tag สามารถเขียนโค้ดได้ดังนี้
// Read
$postTags = $post->tags;
$videoTags = $video->tags;
$postsOfTag = $tag->posts;
$videosOfTag = $tag->videos;
// Create
$post->tags()->attach($tagId);
$post->tags()->attach([1 => ['added_by' => auth()->id()], 3 => []]);
// Update
$post->tags()->updateExistingPivot($tagId, ['added_by' => auth()->id()]);php🔍 ตรวจสอบการมีอยู่ของความสัมพันธ์ (Querying Relationship Existence)#
นอกจากการดึงข้อมูลความสัมพันธ์ออกมาตรง ๆ แล้ว ในงานจริงเรามักจะเจอโจทย์ประเภท “ดึงเฉพาะ Model ที่มี (หรือไม่มี) ความสัมพันธ์ตามเงื่อนไข” อยู่บ่อยมาก ซึ่ง Eloquent เตรียมฟังก์ชันสำหรับกรณีนี้ไว้ให้แล้ว โดยไม่ต้องเขียน subquery หรือ join เองให้ปวดหัว
has() คือฟังก์ชันที่ใช้กรองเฉพาะ Model ที่มีความสัมพันธ์อยู่อย่างน้อย 1 รายการ และยังกำหนดจำนวนขั้นต่ำได้ด้วย
// ดึงเฉพาะ user ที่มี post อย่างน้อย 1 รายการ
$users = User::has('posts')->get();
// ดึงเฉพาะ user ที่มี post ตั้งแต่ 3 รายการขึ้นไป
$users = User::has('posts', '>=', 3)->get();phpส่วน whereHas() จะคล้ายกับ has() แต่เพิ่มความสามารถในการใส่เงื่อนไขเข้าไปในความสัมพันธ์ผ่าน closure ได้ เหมาะกับโจทย์ที่ซับซ้อนขึ้น
use Illuminate\Database\Eloquent\Builder;
// ดึงเฉพาะ user ที่มี post ซึ่งเผยแพร่แล้ว (status = published)
$users = User::whereHas('posts', function (Builder $query) {
$query->where('status', 'published');
})->get();
// ใส่เงื่อนไขจำนวนได้ด้วย เช่น user ที่มี post เผยแพร่แล้วตั้งแต่ 5 รายการขึ้นไป
$users = User::whereHas('posts', function (Builder $query) {
$query->where('status', 'published');
}, '>=', 5)->get();phpในทางกลับกัน หากต้องการดึง Model ที่ “ไม่มี” ความสัมพันธ์ ก็มี doesntHave() และ whereDoesntHave() ให้ใช้คู่กัน
use Illuminate\Database\Eloquent\Builder;
// ดึงเฉพาะ user ที่ยังไม่มี post เลย
$users = User::doesntHave('posts')->get();
// ดึงเฉพาะ user ที่ไม่มี post ซึ่งเผยแพร่แล้ว (อาจมีแต่ draft ก็ได้)
$users = User::whereDoesntHave('posts', function (Builder $query) {
$query->where('status', 'published');
})->get();php🔢 นับความสัมพันธ์ด้วย withCount() และจัดการปัญหา N+1#
ในหัวข้อพื้นฐานเราได้เกริ่นเรื่อง Eager Loading และปัญหา N+1 ไว้แล้ว ในส่วนนี้จะมาขยายให้เห็นภาพแบบรูปธรรมพร้อมตัวอย่างที่จับต้องได้
สมมติว่าเราต้องการแสดงรายชื่อ user พร้อมกับ post ของแต่ละคน หากเขียนแบบ lazy load โดยไม่ใช้ with จะเกิดอะไรขึ้น
// ❌ เกิดปัญหา N+1
$users = User::all(); // 1 query สำหรับดึง user ทั้งหมด
foreach ($users as $user) {
echo $user->posts->count(); // ยิงเพิ่มอีก 1 query ต่อ user 1 คน
}phpหากมี user 100 คน โค้ดข้างบนจะยิง query รวม 101 ครั้ง (1 ครั้งสำหรับดึง user + อีก 100 ครั้งสำหรับ post ของแต่ละคน) นี่คือที่มาของชื่อ N+1 นั่นเอง
ทางแก้คือใช้ Eager Loading ด้วย with() เพื่อให้ Eloquent ดึง post ของทุก user มาในครั้งเดียว
// ✅ แก้ปัญหา N+1 ด้วย Eager Loading
$users = User::with('posts')->get(); // เหลือเพียง 2 query เท่านั้น
foreach ($users as $user) {
echo $user->posts->count();
}phpแบบนี้จะเหลือเพียง 2 query (1 ครั้งสำหรับ user + อีก 1 ครั้งสำหรับ post ของทุกคนรวมกัน) ไม่ว่าจะมี user กี่คนก็ตาม
แต่ถ้าจุดประสงค์ของเราคือต้องการแค่ “จำนวน” ของความสัมพันธ์ ไม่ได้ต้องการตัวข้อมูล post จริง ๆ การดึง post ทั้งหมดมานับถือว่าสิ้นเปลือง
กรณีนี้ควรใช้ withCount() แทน ซึ่งจะให้ค่ามาเป็น attribute ในรูปแบบ {relation}_count
// ดึง user พร้อมจำนวน post ของแต่ละคน
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count; // เข้าถึงผ่าน attribute ชื่อ posts_count
}phpนอกจากนี้ยังใส่เงื่อนไขและตั้งชื่อ alias ให้ count แต่ละชุดได้ รวมถึงมีฟังก์ชัน aggregate ตัวอื่น เช่น withSum(), withMax(), withMin(), withAvg() ให้เลือกใช้
use Illuminate\Database\Eloquent\Builder;
// นับ post ทั้งหมด และนับเฉพาะ post ที่เผยแพร่แล้วแยกอีกชุด พร้อมตั้งชื่อ alias
$users = User::withCount([
'posts',
'posts as published_posts_count' => function (Builder $query) {
$query->where('status', 'published');
},
])->get();
echo $users[0]->posts_count;
echo $users[0]->published_posts_count;php🔗 จัดการ Pivot ของ Many-to-Many ให้ครบ (sync / detach + อ่าน pivot data)#
ในตัวอย่าง Many-to-Many ก่อนหน้านี้เราใช้ attach() กับ updateExistingPivot() ไปแล้ว แต่ในการใช้งานจริงยังมีอีกหลายฟังก์ชันที่ช่วยจัดการความสัมพันธ์ผ่าน Pivot Table ได้สะดวกขึ้น
detach() ใช้สำหรับถอดความสัมพันธ์ออก ซึ่งจะลบเฉพาะ record ในตาราง Pivot ไม่ได้ไปลบตัว Role จริง
// ถอด role เดียวออกจาก user
$user->roles()->detach($roleId);
// ถอดหลาย role พร้อมกัน
$user->roles()->detach([1, 2, 3]);
// ถอด role ทั้งหมดออก (ไม่ใส่ argument)
$user->roles()->detach();phpsync() ใช้สำหรับกำหนดชุดความสัมพันธ์ใหม่ทั้งหมด โดย id ที่ไม่ได้อยู่ใน array จะถูก detach ออกให้อัตโนมัติ เหมาะกับฟอร์มแก้ไขที่ติ๊กเลือก role ได้หลายตัว
// หลังจากนี้ user จะเหลือเฉพาะ role 1, 2, 3 เท่านั้น (ตัวอื่นถูกถอดออก)
$user->roles()->sync([1, 2, 3]);
// แนบ pivot data ไปพร้อมกันได้
$user->roles()->sync([
1 => ['expires_at' => now()->addMonth()],
2 => ['expires_at' => now()->addMonths(3)],
]);phpหากต้องการเพิ่มความสัมพันธ์ใหม่โดย “ไม่ถอด” ตัวเดิมที่มีอยู่ออก ให้ใช้ syncWithoutDetaching() แทน
// เพิ่ม role 4, 5 เข้าไป โดย role เดิมยังอยู่ครบ
$user->roles()->syncWithoutDetaching([4, 5]);phpและเนื่องจากเราประกาศ withPivot(['assigned_by', 'expires_at']) ไว้ที่ Model ตั้งแต่ต้น จึงสามารถอ่านข้อมูลใน Pivot Table ผ่าน property pivot ได้โดยตรง
foreach ($user->roles as $role) {
echo $role->pivot->expires_at; // อ่านค่า expires_at จากตาราง role_user
echo $role->pivot->assigned_by;
}php🎯 ใส่เงื่อนไขให้ Eager Loading และโหลดแบบซ้อนชั้น#
โดยปกติ with() จะดึงความสัมพันธ์มาทั้งหมด แต่หลายครั้งเราอยากดึงมาเฉพาะที่ตรงเงื่อนไขเท่านั้น เพื่อลดข้อมูลที่ไม่จำเป็น
ทำได้โดยส่ง closure เข้าไปกำกับความสัมพันธ์นั้น
// ดึง user มาพร้อม post แต่เอาเฉพาะ post ที่เผยแพร่แล้ว
$users = User::with(['posts' => fn ($query) => $query->where('status', 'published')])->get();phpและหากความสัมพันธ์มีหลายชั้นซ้อนกัน เราสามารถ eager load แบบ nested ได้ด้วยการใช้จุด (.) คั่นชื่อความสัมพันธ์
เช่น ต้องการดึง post พร้อมเจ้าของ post และ profile ของเจ้าของในคราวเดียว
// โหลด post -> user (เจ้าของ) -> profile แบบซ้อนชั้นในครั้งเดียว
$posts = Post::with('user.profile')->get();
// ใส่หลายความสัมพันธ์พร้อมกันได้
$posts = Post::with(['user.profile', 'comments'])->get();php🔑 กำหนด Foreign Key เองให้ belongsTo#
โดยค่าเริ่มต้น Eloquent จะเดาชื่อ foreign key จากชื่อความสัมพันธ์ให้อัตโนมัติ (เช่น ความสัมพันธ์ country จะมองหาคอลัมน์ country_id)
แต่ถ้าโครงสร้างตารางของเราใช้ชื่อคอลัมน์ที่ต่างออกไป ก็สามารถระบุเองได้ผ่าน argument ตัวที่ 2 (foreign key) และตัวที่ 3 (owner key หรือ primary key ของตารางปลายทาง)
// belongsTo(RelatedModel::class, foreign_key, owner_key)
public function country() {
return $this->belongsTo(Country::class, 'country_id', 'id');
}php📚 สรุปและแนวทางต่อยอด#
จากตัวอย่างจะเห็นว่า หากเราสามารถใช้ Eloquent Relationships ได้อย่างถูกต้อง จะทำให้โค้ดสั้นขึ้นและอ่านง่ายขึ้นมาก ทำให้เราบริหารจัดการความสัมพันธ์ของ Model ได้ดียิ่งขึ้น แต่ทั้งนี้ในการใช้งานโปรเจกต์จริง อาจมีเงื่อนไขที่ซับซ้อนมากกว่านี้อีกมาก ซึ่งใน Document ของ Laravel ก็ได้มีการอัปเดตอยู่อย่างสม่ำเสมอ ดังนั้นหากต้องการใช้งาน Eloquent Relationships ให้ชำนาญจะต้องศึกษาเอกสารควบคู่ไปด้วย เพียงเท่านี้เราก็สามารถบริหารจัดการโค้ดของเราให้เป็นระเบียบและพร้อมเรียกใช้ข้อมูลได้แล้ว
🥰