iT邦幫忙

2024 iThome 鐵人賽

DAY 22
1
Modern Web

後端菜雞仔想學 Laravel系列 第 22

hasMany & belongsTo:建立模型關聯

  • 分享至 

  • xImage
  •  

資料表規劃:products 表

欄位名 說明 格式 包含備註內容
id ID unsignedBigInteger 自動遞增
type_id 產品分類 unsignedBigInteger foreign 外鍵關聯 接受空值 預設值為空
product_name 產品名稱 varchar(255)
product_description 產品描述 text
price 產品價錢 unsignedInteger
created_at 新建時間 timestamp
updated_at 更新時間 timestamp

Migration & 資料庫設計 的文章中,有先說 create_products_table.php
的 type_id 一開始先不做「包含備註內容」的部份,等到後續新增 types 表時再做。

因為我想先以最基礎的去介紹,接著到模型關聯時再做,可能會更好理解。
(菜雞仔如我,當時也是這樣理解起來的)

步驟建議

但我會建議如果一開始在規劃資料庫時,就很清楚知道哪些表需要做 foreign key 的話,可以調整步驟優先建立,之後如果有沒思考到的部份再進行調整,操作起來會比較流暢。

如果是我的步驟:

  1. 我首先會先建立好 type 的基本檔案。
  2. 再接著建立 Product 的基本檔案。
  3. 然後再做模型關聯。

(這只是我自己的想法,每個人的操作可能不同XD)

目前需求

要在已經執行過 migration 的 products 表中加入 foreign key。

建立新的 migration 來更新 products 表

php artisan make:migration add_foreign_key_to_products_table --table=products

修改新的 migration 檔案

public function up(): void
{
    Schema::table('products', function (Blueprint $table) {
        // 加入外鍵關聯,當 type_id 被刪除時設定為 null
        $table->foreignId('type_id')
              ->nullable() // 允許為空
              ->constrained()
              ->onDelete('set null')
              ->comment('產品分類');
    });
}

public function down(): void
{
    Schema::table('products', function (Blueprint $table) {
        // 先移除外鍵再移除欄位
        $table->dropForeign(['type_id']);
    });
}

onDelete('set null')

是希望如果 types 表某個 id 被刪除時,所有引用此 id 的資料會將其 type_id 自動設定為 null,之後可再將其產品重新分配類別!
因此需求,所以也將 type_id 設定為可接受 null。

執行 migration

php artisan migrate

這樣就會成功在 products 表的 type_id 欄位加入外鍵關聯到 types 表,並且當 type 被刪除時,type_id 會被設為 null。
最重要的是不會影響原本 products 表中的資料。

更多相關資訊可以參考官方文件:


什麼是模型關聯?

模型關聯(Eloquent Relationships)就是指資料表之間的邏輯關係。

例如:
一個「產品」可能屬於某一個「類別」,而一個「類別」可能包含多個「產品」。

所以透過模型關聯,我們可以很方便地從資料庫中取出這些關聯資料,而不需要寫太多的 SQL 查詢。

常見的關聯類型

以下整理 GPT 提供的範例及基本應用:

一對一(hasOne)

一個模型只能與另一個模型擁有唯一的關聯。

範例:每位「使用者」有唯一的「個人資料」。

class User extends Model {
    public function profile() {
        return $this->hasOne(Profile::class);
    }
}

基本應用:

$user = User::find(1);
$profile = $user->profile; // 取得該使用者的個人資料

一對多(hasMany)

一個模型可以擁有多個關聯的資料。

範例:一個「類別」包含多個「產品」。

class Category extends Model {
    public function products() {
        return $this->hasMany(Product::class);
    }
}

基本應用:

$category = Category::find(1);
$products = $category->products; // 取得該類別下的所有產品

多對一(belongsTo)

這是「一對多」的反向關聯。例如:產品屬於某個類別。

範例:一個「產品」屬於一個「類別」。

class Product extends Model {
    public function type() {
        return $this->belongsTo(Type::class);
    }
}

基本應用:

$product = Product::find(1);
$type = $product->type; // 取得該產品所屬的類別

多對多(belongsToMany)

當兩個模型之間可以有多個關聯時使用。
例如:學生可以選修多門課程,而課程也可以有多個學生。

範例:學生和課程是多對多的關係。

class Student extends Model {
    public function courses() {
        return $this->belongsToMany(Course::class);
    }
}

基本應用:

$student = Student::find(1);
$courses = $student->courses; // 取得該學生的所有課程

多態關聯(Polymorphic Relationships)

允許不同的模型共用同一個關聯模型。
例如:文章和影片都可以有評論。

範例:文章和影片都有評論。

class Comment extends Model {
    public function commentable() {
        return $this->morphTo();
    }
}

基本應用:

$comment = Comment::find(1);
$commentable = $comment->commentable; // 這可以是文章或影片

查詢關聯資料

延遲加載(Lazy Loading)

當你訪問關聯資料時,它會自動執行查詢。

$product = Product::find(1);
$category = $product->category; // 這會在訪問 category 時執行查詢

預先載入(Eager Loading)

使用 with() 方法可以預先載入關聯資料,避免多次查詢(N+1 查詢問題)。

$products = Product::with('category')->get(); // 一次性載入產品及其類別

關聯命名規則

命名關聯方法時,遵循單數或複數的規則。
例如:

  • hasOne() 用單數
  • hasMany() 用複數

這樣更符合直觀想法,可讀性更高。


建立模型關聯

  • 使用一對多(hasMany)和多對一(belongsTo)

修改 Type.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Type extends Model
{
    use HasFactory;
    protected $fillable =[
        'name',
        'sort',
    ];
    /**
     * 取得類別的商品
     */
    public function product() {
        return $this->hasMany('App\Product', 'type_id', 'id');
    }
}

修改 Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    protected $fillable = [
        'type_id',
        'product_name',
        'product_description',
        'price',
    ]; 
    /**
     * 取得商品的類別
     */
    public function type() {
        return $this->belongsTo('App\Type');
    }
}

使用 Postman 測試 Type API

新增類別:

https://ithelp.ithome.com.tw/upload/images/20241006/20169300EsRtwsp8Sc.jpg

查詢所有類別:

https://ithelp.ithome.com.tw/upload/images/20241006/20169300aCJbetGsj3.jpg

查詢特定類別:

https://ithelp.ithome.com.tw/upload/images/20241006/201693002KRZ7vYgJ8.jpg

更新特定類別:

https://ithelp.ithome.com.tw/upload/images/20241006/201693008fkdl6v0V3.jpg

刪除特定類別:

https://ithelp.ithome.com.tw/upload/images/20241006/20169300PjpgTkcYTP.jpg

以上請求皆測試成功!


上一篇
分類 CRUD:指派另一位店長!
下一篇
API Resources:將你的第一手資料進行包裝
系列文
後端菜雞仔想學 Laravel30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言