Eloquent Relationship 快速入門

Eloquent Relationship 快速入門

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

示範表格結構

表格結構

命名慣例

  • 外鍵欄位為對應表格模型名加上 "_id"
  • 關係函式的名稱不要與模型的屬性相衝突,會導致無法解析
  • 圖中所命名的 Order_Item 並未完全按照命名慣例,更好的取法是 Item_Order,因為 I 在26個字母中在 O 之前

以下寫法均採用語法糖,限定外鍵欄位與主鍵欄位須採用預設格式,例如 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([$item,$item2]);

//取用關聯表格的資料
$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();

分享這篇文章:
 

訂閱電子報,索取 Laravel 學習手冊

價值超過 3000 元,包含常用 Laravel 語法與指令!

一小時免費求職講座

3個應徵軟體工程師前該知道的秘訣

取得免費課程連結

Laravel 百萬年薪特訓營

從最基礎的 PHP 語法開始,包含所有你該知道的網頁基礎知識,連同 Laravel 從零開始一直到實戰,最後還將告訴你如何找好工作,讓你及早擁有百萬年薪