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();