Devfavor

Back

การใช้งาน Eloquent Relationships - เพื่อดึงข้อมูลใน Laravel อย่างมีประสิทธิภาพBlur image

🤔 ทำไม 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 อีกที)
  • Polymorphic - หรือ Polymorphism หมายถึง การมีหลายรูปแบบ ซึ่งในกรณีของ Eloquent Relationships นี้คำว่า Polymorphic มีความหมายเฉพาะตัวคือ การที่ Model หนึ่งสามารถมีความสัมพันธ์แบบ One-to-Many กับหลาย Model ได้ (หลายรูปแบบ) ผ่าน field เดียวกัน โดยไม่ต้องเพิ่ม foreign key

ในความเป็นจริงแล้วคำศัพท์เหล่านี้มีรายละเอียดเฉพาะตัวอีกมาก แต่ในที่นี้ขอให้เข้าใจพื้นฐานโดยคร่าวก่อน ก็เพียงพอที่จะดีไซน์ความสัมพันธ์ได้อย่างมีประสิทธิภาพแล้ว

📝 ประเภทของ 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_table
bash

หลังจากสร้างไฟล์ 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();
    });
}
php

create_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');
    });
}
php

create_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();
    });
}
php

create_roles_table

public function up(): void {
    Schema::create('roles', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->string('label')->nullable();
        $table->timestamps();
    });
}
php

create_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']);
    });
}
php

create_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');
    });
}
php

create_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']);
    });
}
php

create_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');
    });
}
php

create_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();
    });
}
php

create_tags_table

public function up(): void {
    Schema::create('tags', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->timestamps();
    });
}
php

create_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 migrate
bash

เป็นอันเรียบร้อยสำหรับการเตรียมฐานข้อมูล จากนั้นให้เตรียม 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 Tag
bash

จากนั้นแก้ไขไฟล์ของ 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->hasOneThrough(Post::class, User::class)->latestOfMany();
    }
}
php

User.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'); }
}
php

Profile.php

class Profile extends Model
{
    protected $fillable = ['user_id', 'bio', 'phone'];

    public function user() {
        return $this->belongsTo(User::class);
    }
}
php

Role.php

class Role extends Model
{
    protected $fillable = ['name','label'];

    public function users() {
        return $this->belongsToMany(User::class)->withTimestamps();
    }
}
php

Post.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']); }
}
php

Comment.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); }
}
php

Video.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']); }
}
php

Photo.php

class Photo extends Model
{
    protected $fillable = ['path','alt'];

    public function imageable() {
        return $this->morphTo();
    }
}
php

Tag.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()->latestOfMany()->first();

// 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

📚 สรุปและแนวทางต่อยอด#

จากตัวอย่างจะเห็นว่า หากเราสามารถใช้ Eloquent Relationships ได้อย่างถูกต้อง จะทำให้โค้ดสั้นขึ้นและอ่านง่ายขึ้นมาก ทำให้เราบริหารจัดการความสัมพันธ์ของ Model ได้ดียิ่งขึ้น แต่ทั้งนี้ในการใช้งานโปรเจกต์จริง อาจมีเงื่อนไขที่ซับซ้อนมากกว่านี้อีกมาก ซึ่งใน Document ของ Laravel ก็ได้มีการอัปเดตอยู่อย่างสม่ำเสมอ ดังนั้นหากต้องการใช้งาน Eloquent Relationships ให้ชำนาญจะต้องศึกษาเอกสารควบคู่ไปด้วย เพียงเท่านี้เราก็สามารถบริหารจัดการโค้ดของเราให้เป็นระเบียบและพร้อมเรียกใช้ข้อมูลได้แล้ว 🥰

Code, coffee, and calm — the holy trinity of a good day. ☕

การใช้งาน Eloquent Relationships - เพื่อดึงข้อมูลใน Laravel อย่างมีประสิทธิภาพ
Author Coffee Stack
Published at August 11, 2025