Eloquent Relationships ชำแหละความสัมพันธ์อันน่าสับสนงงงวย (Part I) Has One?, Has Many??, Belongs To ??? Laravel
ขอเกริ่นก่อนเลยว่ายังเป็นโปรแกรมเมอร์อ่อนหัดที่เพิ่งเริ่มเขียนโปรแกรมจริงๆจังๆได้ไม่ถึง 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');
});
🔥 การเก็บข้อมูลของ 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.