---
# System prepended metadata

title: Eloquent Relationship

---

# Eloquent Relationship
說明 Laravel 自帶的 Eloquent，如何進行一對一.一對多.多對多等 CRUD操作

Laravel官方說明

示範表格結構


命名慣例
外鍵欄位為對應表格模型名加上 “_id”
關係函式的名稱不要與模型的屬性相衝突，會導致無法解析
以下寫法均採用語法糖，限定外鍵欄位與主鍵欄位須採用預設格式，例如 user_id與id。

一對一
每個Model實例只屬於/擁有另一個Model實例，例如每個商品(Item)只屬於一個分類(Cgy)

表格設計上，會增加所對應Model的流水號，作為外鍵。舉例來說，要說明某商品屬於哪個類別，會在商品表格上加入cgy_id欄位

撰寫流程:
Step 1.修改Migration與表格
Step 2.撰寫關係函式
Step 3.測試關係函式

主鍵和外鍵都應採用bigInteger，否則執行migrate時會出現錯誤

修改Migration範例
//在up()裏頭
Schema::create('items', function (Blueprint $table) {
    ...
    $table->unsignedBigInteger('cgy_id')->index();
    $table->foreign('cgy_id')->references('id')->on('cgies')->onDelete('cascade');
});
//在down()裡面
Schema::table('items', function(Blueprint $table){ 
    $table->dropForeign(['cgy_id']);
});
撰寫Model關係函式範例
\\App\Models\Item.php

//此商品"屬於"哪個類別
public function cgy()
{
    //如未按照Laravel的命名慣例，則需提供外鍵與主鍵欄位名稱
    //外鍵命名慣例： 對應模型 + _id，例如 cgy_id
    //主鍵命名慣例： id
    return $this->belongsTo(\App\Models\Cgy::class);
}
belongsTo()函式的寫法可改成belongsTo(’\App\Models\Cgy’)
如果外鍵欄位名稱不是cgy_id，就需要在belongsTo()的第二參數加以說明
如果主鍵欄位名稱不是id，就需要在belongsTo()的第三參數加以說明
以下依此類推

使用關係函式
//修改關聯主鍵
$item->cgy()->asssociate($another_cgy);
$item->save();

//移除關聯
$item->cgy()->dissociate();
$item->save();

//取用關聯表格的資料
$item->cgy->id;

一對多
每個Model實例屬於/擁有多個實例，例如每個使用者(User)擁有多筆訂單(Order)

表單設計上，會在另一個對應表單增加自己的流水號，作為外鍵。舉例來說，要說明使用者擁有哪些訂單，會在訂單表格上加入user_id欄位

撰寫流程:
Step 1.修改Migration與表格
Step 2.撰寫關係函式
Step 3.測試關係函式

修改Migration範例
同一對一，如果已經做過就無須再做

撰寫Model關係函式範例
\\App\Models\Cgy.php

//此分類擁有那些商品
public function items()
{
    //如未按照Laravel的命名慣例，則需提供外鍵與主鍵欄位名稱
    //外鍵命名慣例： 對應模型 + _id，例如 cgy_id
    //主鍵命名慣例： id
    return $this->hasMany(\App\Models\Item::class);
}
hasMany()函式的寫法可改成hasMany(’\App\Models\Item’)
如果外鍵欄位名稱不是cgy_id，就需要在hasMany()的第二參數加以說明

使用關係函式
如果加入已存在的關係，不會產生新的變化，也不會報錯

//針對關聯表格進行查詢
$items = $cgy->items()->where('enabled',true)->get();

//修改關聯主鍵
$cgy->items()->save($item);
$cgy->items()->saveMany([$task,$task2]);

//取用關聯表格的資料
$cgy->items;
多對多
類似一對多，每個Model實例屬於/擁有多個Model實例，但反過來也是相同的一對多關係。例如每個商品(Item)屬於多筆訂單(Order)。反過來，每個訂單也擁有多個商品。

表單設計上，會建立一個新的表單作為pivot，來記錄多對多關係。舉例來說，商品與訂單的多對多關係就會建立名為item_order的表格，依照字母順序來排列

撰寫流程:
Step 1.修改Migration與表格，建立pivot表格
Step 2.撰寫關係函式
Step 3.確定有假資料可用(Option)，否則寫Seeder&Factory
Step 3.測試關係函式

pivot表格範例
public function up()
    {
        Schema::create('item_order', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('item_id')->index()->unsigned();
            $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade');
            $table->bigInteger('order_id')->index()->unsigned();
            $table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade');
            $table->integer('qty')->default(1);
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::table('item_order', function (Blueprint $table) {
            $table->dropForeign(['item_id','order_id']);
        });
        Schema::dropIfExists('item_order');
    }
關係函式範例
withTimestamps() 新增關係時一併維護pivot表格的created_at與updated_at欄位
withPivot(‘col_name1’,‘col_name2’) 同時帶出pivot表格的其他欄位

//app/Models/Item.php

    //此商品屬於那些訂單
    public function orders(){
        return $this->belongsToMany(\App\Models\Order::class)->withTimestamps();
    }
如果Laravel沒有正確地使用我們的pivot表格名稱，可在belongsToMany()的第二參數說明正確的pivot表格名稱

//app/Models/Order.php

    //此訂單擁有那些商品
    public function items(){
        return $this->belongsToMany(\App\Models\Item::class)->withTimestamps()->withPivot('qty');
    }
使用關係函式範例
語法:

新增關係 save(對應Model參考,[pivot表格額外欄位資料])
新增關係 attach(對應Model流水號陣列,[pivot表格額外欄位資料])
移除關係 detach([對應Model流水號]) 如不提供流水號，表示移除所有關係
同步關係 sync(對應Model流水號陣列)

//取出該訂單的所有商品
$order->items;

//取出訂單某商品的數量，需搭配withPivot('qty')使用
$order->items()->first()->pivot->qty;

//用商品參考將該商品加入訂單
$order_1->items()->save($item_1);

//用商品參考將該商品加入訂單，pivot表的qty為2
$order_1->items()->save($item_1,['qty'=>2]);

//用商品主鍵將該商品加入訂單
$order_2->items()->attach([1,2]);

//用商品主鍵將該商品移出訂單
$order_1->items()->detach(1);

//重置該訂單的商品內容為1,2,3.4
$order_1->items()->sync([1,2,3,4]);
pivot屬性名稱可以根據你的喜好做修改，作法為在belongsToMany()後面加一個as(‘新別名’)，之後就可以用新別名取代pivot

//app/Models/Order.php

    //此訂單擁有那些商品，將pivot改名為detail
    public function items(){
        return $this->belongsToMany(\App\Models\Item::class)->as('detail');
    }
    
$order->items()->detail->qty

常見問題
Q1.關係函式在使用時到底需不需要加小括號呢?

A:要看你的目的，如果你的查詢條件都已經完成，想直接開始查詢就不用加。反之如果需要進一步的查詢，比如下where()，就需要加

關聯查詢
//找出分類中至少有1個商品的
$cgies = Cgy::has(‘items’)->get();

//找出分類中至少有5個以上商品的
$cgies = Cgy::has(‘items’,’>=’,5)->get();

cgies=Cgy::whereHas(′items′,function(query){
//找出分類中至少擁有商品價格為8000以上的
$query->where(‘price’,’>’,8000);
})->get();

進階技巧
連帶更新父紀錄的更新時間
即當子紀錄(如Item)的欄位被修改之後，同步修改父紀錄(如Cgy)的updated_at欄位

\\App\Models\Item.php

protected $touches = ['cgy'];
積極性載入(Eager Loading)
當查詢資料時，連帶把關係資料一起帶出來，以簡化查詢作業。預設為消極性載入(Lazy Loading)，也就是當使用該屬性時才進行查詢

//取出所有的分類及其擁有的歌曲資料
$cgies = Cgy::with('songs')->get();
//取出所有的分類並算出其擁有的歌曲數量
$songs_count = Cgy::withCount('songs')->get();
Nested Eager Loading
一併往下查詢到再下一層的Model，比如同時查詢該書的作者的聯絡單

$book = App\Models\Book::with(‘author.contacts’)->get();

Eager loading限定欄位
一併先查詢關聯的Author資料，但只先取id和name欄位

$users = App\Models\Book::with(‘author:id,name’)->get();

Constraints Eager Loading
一併查詢posts表格的時候，同時對posts表格查詢加入title欄位內有first字樣的條件


users=App\Models\User::with([′posts′=>function(query){
$query->where(‘title’,‘like’,’%first%’);
}])->get();