--- tags: laravel --- # Eloquent Relationship 說明 Laravel 自帶的 Eloquent,如何進行一對一.一對多.多對多等 CRUD操作 [Laravel官方說明](https://laravel.com/docs/7.x/eloquent-relationships) ### 示範表格結構 ![](https://i.imgur.com/H0zhYIF.jpg) ## 命名慣例 * 外鍵欄位為對應表格模型名加上 "_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'); //新寫法 $table->foreignId('cgy_id')->constrained(); }); //在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();