Eloquent Relationships ชำแหละความสัมพันธ์อันน่าสับสนงงงวย (Part I) Has One?, Has Many??, Belongs To ??? Laravel

Sopita Jullaprasertsak
Yeeraf Co., Ltd.
Published in
4 min readJan 14, 2020

ขอเกริ่นก่อนเลยว่ายังเป็นโปรแกรมเมอร์อ่อนหัดที่เพิ่งเริ่มเขียนโปรแกรมจริงๆจังๆได้ไม่ถึง 1 ปี แต่อยากมาแชร์ความรู้เล็กๆน้อยๆที่เราเคยงงงวย สับสนในช่วงแรกๆที่ได้ลองเขียน Laravel นั่นคือเรื่อง Eloquent Relationship นั่นเอง (ขอตั้งชื่อเรื่องว่า ความสัมพันธ์ที่น่าสับสน 555555)

⁉️ ความสัมพันธ์ในชีวิตก็ว่าสับสนแล้ว ความสัมพันธ์ใน Laravel ก็สับสนพอๆกัน(ในช่วงแรกๆ) แต่จริงๆแล้วถ้าเราจับจุดได้ ก็จะพบว่า…`เอ๊ะ ทำไมมันง่ายจัง!`

ตัวฉันในตอนนั้น

ขอแบ่งปันความรู้ในแบบภาษาง่ายๆนะคะ เพราะตัวเองก็ยังไม่ชำนาญศัพท์เชิงเทคนิคสักเท่าไหร่ หากผิดพลาดแล้วอยากเสริมอยากแชร์ตรงไหน สามารถแย้งได้เลยนะคะ ยินดีแก้ไขและแชร์ข้อมูลที่ถูกต้องต่อไปค่ะ บทความนี้น่าจะเหมาะกับ beginner ที่เพิ่งจับ laravel นะคะโอเค..มาเริ่มกันเลย 💥

One to One | ความสัมพันธ์แบบ 1–1 👸🤴

Case: สมมติว่า เราเป็น`เจ้าของ`(Owner) 👴 มี`นาฬิกาข้อมือ`(Watch) 1 เรือน (ไม่นับยืมเพื่อนนะ)

💞 Relationships:

  • เจ้าของ 1 คนมีนาฬิกาได้ 1 เรือน → จะได้เป็น `1 Owner has one Watch`
  • นาฬิกา 1 เรือนมีเจ้าของได้แค่ 1 คน → `1 Watch belongs to Owner`
  • Belongs to แปลว่า “ ขึ้นอยู่กับ ” ในบริบทนี้ก็เลยอาจหมายความได้ว่า การมีนาฬิกา 1 เรือนเนี้ยก็ขึ้นอยู่กับเจ้าของ 1 คน

🧠 Eloquent Models:

พอเรารู้ relationships ของคู่นี้แล้วเนี้ย ก็ลองมาเขียนโค้ดกัน โดยจะมี 2 model — Owner กับ Watch

Class Owner //model ของ Ownerpublic function watch() {    return $this->hasOne(\App\Watch::class);}

ลองอ่านโค้ดนี้ดูนะคะ ตีความหมายง่ายๆ ด้วยการอ่านจากบนลงล่างปกติเลย

“Owner จะมีความสัมพันธ์กับ Watch ด้วยการสร้าง function ขึ้นมาแล้วข้างใน function ก็สร้างความสัมพันธ์ของ `ตัวมันเอง`($this ก็คือ Owner หรือเจ้าของ) ที่มี นาฬิกา 1 เรือน(hasOne Watch)”

ในทางกลับกัน model ของ Watch ก็จะได้คล้ายๆกัน แค่สลับที่ระหว่าง Owner กับ Watch และเปลี่ยนจาก hasOne เป็น belongsTo

Class Watch //model ของ Ownerpublic function owner() {     return $this->belongsTo(\App\Watch::class);}
  • ****ขอดอกจันทร์ไว้สัก 10 กะโหลกตรงนี้ว่า ใช้ belongsTo ที่ model ไหน database ของตารางนั้นต้องเก็บ id ของอีกตัวนึงด้วย
  • ดังนั้น ตาราง Watch ต้องเก็บ owner_id ไว้ด้วย! เพื่อทั้ง 2 ตารางสามารถที่จะเชื่อมกันได้ (ส่วนตาราง Owner นั้นไม่ต้องมี watch_id)

🔥 Using: เวลาจะนำไปใช้ก็สามารถเขียนได้ดังนี้เลยค่ะ

$owner->watch;
//////////////
$watch->owner;

🌈 Summarize:

  • Has One คู่กับ Belongs to เสมอ
  • ตัวที่ใช้ belongsTo ต้องเก็บ id ของอีกตัวเสมอ
  • หลายคนอาจจะสงสัยว่า แล้วรู้ได้ไงว่าอันนี้ใช้ has one แล้วอีกอันใช้ belongs to ล่ะ ?
  • สิ่งที่ดูใหญ่กว่า มี Power กว่า ใช้ has one, สิ่งที่เป็นรองกว่า ดูเป็นผู้น้อยใช้ belongs to

ยกตัวอย่างอีกนิดหน่อยเพื่อความเข้าใจมากขึ้น

  • 1 user มี 1 email → user ก็คือเป็นเจ้าของ Email ดูเป็นใหญ่กว่า ดูเป็นเจ้าข้าวเจ้าของ email ก็เลยใช้ has one ส่วน Email ขึ้นอยู่กับ user ใช้ belongs to

ประมาณนี้ พอจะเก็ทกันใช่มั้ยคะ งั้นไปต่อกันเลย

One to Many| ความสัมพันธ์แบบ 1–หลายสิ่ง 🧒🐩🐩

โอเค พอผ่าน 1–1 มาแล้ว ต่อไปนี้เป็นต้นไปจะขออธิบายกระชับๆแล้วกันนะคะ เพราะเนื้อหาบางส่วนได้อธิบายละเอียดใน one to one แล้ว ลองคิดภาพตามตามสถานการณ์ต่อไปนี้นะคะ

Case: 1 `บริษัท`(Company) มีหลาย`แผนก` (Department)

1 `แผนก`(Department) มีแค่ 1`บริษัท` (Company)

*เป็นเพียงเคสตัวอย่างนะคะ ในความเป็นจริง 1 แผนกอาจมีได้หลายบริษัทก็ได้ แล้วแต่โจทย์และมุมมองด้วย

💞 Relationships:

  • 1 บริษัทมีได้หลายแผนก → จะได้เป็น `1 Company has many Departments`
  • 1 แผนกมีบริษัทได้เพียง 1 บริษัท → `1 Department belongs to Company`

*สังเกตได้ว่า แผนกซึ่งอยู่ภายใต้บริษัท จะใช้ belongs to

🧠 Eloquent Models:

2 model — Company กับ Department

Class Company //model ของ Companypublic function departments() {     return $this->hasMany(\App\Department::class);}

ตีความจากโค้ดได้ว่า “Company(model) มีหลาย(hasMany) Departments” แล้วสังเกตุได้ว่า department เติม s ไว้นะคะ เพื่อจะให้เข้าใจได้แบบทันทีว่ามันคือ has many เราจะได้เอาไปใช้ง่ายๆ เข้าใจง่ายและไม่สับสนด้วยค่ะ

มาดู model ของตัว Department กันบ้าง

Class Department//model ของ Departmentpublic function company() {     return $this->belongsTo(\App\Company::class);}
  • ตาราง Department ต้องเก็บ company_id ไว้ด้วย!!

🔥 Using:

$company->departments;
/////////////////////
$department->company;

🌈 Summarize:

  • Has Many คู่กับ Belongs to เสมอ! (เหมือน Has One เลยย)
  • เหมือนเดิมค่ะ ตัวที่ใช้ belongsTo ต้องเก็บ id ของอีกตัวเสมอ

Many to Many| ความสัมพันธ์แบบหลาย-หลายสิ่ง 👨‍👩‍👧‍👦

พอมาถึงตรงนี้ บอกเลยค่ะว่า 1–1 กับ 1-Many เป็นแค่น้ำจิ้มๆ5555 Many-Many ก็จะซับซ้อนขึ้นมาอีก 1 สเตป ถ้าพร้อมแล้ว ลุยเคสต่อไปกันเลย

Case: 1 `ผู้ใช้`(User) มีได้หลาย`บทบาทหน้าที่` (Role) ในขณะเดียวกัน 1 หน้าที่นี้ก็มีได้หลาย user เช่นกัน

💞 Relationships:

  • 1 User มีได้หลาย Roles → จะได้เป็น `1 User belongs to many Roles`
  • 1 Role ก็มีได้หลาย Users → `1 Role belongs to many Users`

*จงจำไว้นะคะว่า Many-Many จะไม่ใช้ has many เพราะทั้งคู่ขึ้นอยู่กับกันและกันก็เลยใช้เป็น belongs to many

🧠 Eloquent Models:

Many-Many พิเศษจาก 2 ความสัมพันธ์ด้านบนหน่อยตรงที่ มี 2 models แต่มี 3 tables!

2 model —User กับ Role

3 table — users, roles และ role_user(ตาราง role_user ที่เพิ่มขึ้นมาจะเรียกว่า Pivot table ซึ่งจะเก็บทั้ง role_id และ user_id)

User Model.

Class User //model ของ Userpublic function roles() {     return $this->belongsToMany(\App\Role::class);}

Role Model. ก็จะหน้าตาคล้ายๆกันเลยค่ะ

Class Role//model ของ Rolepublic function users() {     return $this->belongsToMany(\App\User::class);}

ทั้งสองตารางไม่ต้องเก็บ id ของกันและกันไว้ แต่แต่แต่! เราต้องสร้างตารางเพิ่งอีก 1 ตารางที่ชื่อว่า role_user เพราะเมื่อเราทำ relationship ให้มัน belongsToMany กันแล้ว มันจะรู้โดยอัตโนมัติว่าจะมีตารางเพิ่มขึ้นมาชื่อ role_user เพื่อเก็บ id ของ role_id และ user_id

อ้าว แล้วมันรู้ได้ไงว่าตารางที่สร้างขึ้นชื่อ role_user? มันรู้โดยการที่นำชื่อของแต่ละ model มาเรียงตามลำดับตัวอักษร (a-z) ถ้าตัวอักษรขึ้นต้นตัวไหนมาก่อนก็เอาตัวนั้นไว้ข้างหน้า ตามด้วย _ แล้วต่อด้วยอีกตัว

หรือถ้าหากเราไม่อยากได้ชื่อ role_user ก็สามารถเปลี่ยนได้เหมือนกันโดยการตั้งชื่อใหม่ใน parameter ที่ 2 เช่น

Class Role//model ของ Rolepublic function users() {   return $this->belongsToMany(\App\User::class, 'user_has_role');}

โดยตาราง pivot ที่สร้างขึ้นจะไม่ insert พวก timestamps (created_at, updated_at)ให้ หากเราต้องการที่จะ insert timestamps ต้องเพิ่ม withTimestamps() ไปด้วยค่ะ

Class Role//model ของ Rolepublic function users() {    return $this->belongsToMany(\App\User::class)
->withTimestamps();
}

และอีก 1 สิ่งที่ควรจะทำเมื่อสร้าง migration นั่นก็คือ ต้อง reference id นั้นๆไปยังตารางของแต่ละตัวด้วย เพื่อถ้าเกิดว่า id ของตารางหลักถูกลบไป → id ที่อยู่ใน Pivot table ก็จะให้ถูกลบไปด้วย (=On Delete Cascade)

Schema::create('role_user', function (Blueprint $table) {
$table->increments('id');

$table->unsignedInteger('user_id')->index();
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');


$table->unsignedInteger('role_id')->index();
$table->foreign('role_id')
->references('id')
->on('roles')
->onDelete('cascade');

});
สภาพใครหลายๆคนในตอนนี้ 5555 อีกนิด ฮึ้บไว้!

🔥 การเก็บข้อมูลของ user และ role เข้า database

$user = App\User::find(1);

$user->roles()->attach([$role1->id, $role2->id]);

จากโค้ดข้างบน user ที่มี id = 1 จะเก็บ role_id ของ $role1 และ $role2 เพิ่ม record ลงในตาราง role_user ใน database โดยใช้ attach ซึ่งหน้าตาของตาราง role_user ที่ถูกเพิ่มเข้าไปแล้วก็จะหน้าตาประมาณนี้ (จินตนาการว่ามันคือตารางนะคะ แฮะๆ)

user_id__|__role_id

___1____|____1___

___1____|____2___

นอกจาก attach แล้ว ก็ยังมี sync อีกตัวที่เอาไว้เก็บข้อมูลความสัมพันธ์แบบ many-many แต่เจ้า sync เป็นการเก็บโดยป้องกันไม่ให้ record ที่เพิ่มเข้าไปซ้ำกัน

$user = App\User::find(1);

$user->roles()->sync([$role1->id, $role2->id]);

user_id__|__role_id

___1____|____1___

̶_̶_̶_̶1̶_̶_̶_̶_̶|̶_̶_̶_̶_̶1̶_̶_̶_̶

🔥 Using: หากต้องการ Retrive ก็เรียกง่ายๆเลย

// ต้องการดู user คนนี้ว่ามีหน้าที่อะไรบ้าง
$user->roles;
// ต้องการดูหน้าที่นี้ว่ามี users คนไหนอยู่ในหน้าที่นี้บ้าง
$role->users;

The End. 🚀

จบแล้ววว เยยยยยย้! จริงๆมีรายละเอียดยิบย่อยเยอะมากกกกกก นี่เป็นเพียงส่วนนึงที่พอเป็นพื้นฐานให้พอเข้าใจแล้วเอาต่อยอดได้ จะได้ไม่เคว้งเกินไป 😆 อ่านเต็มๆได้ที่ doc ของ laravel เลย เจอกันใหม่พาร์ทหน้ากับความสัมพันธ์ที่งงกว่าเดิม บัย!

📚 Reference.

--

--

Sopita Jullaprasertsak
Yeeraf Co., Ltd.

|'m a Front-end Web Developer 🔥🤓 who is in love with drawing and design 📋🖍️