什麼時候使用

當你有多對多的 tables,需要簡單、輕鬆、快速處理中間 pivot table 資料時

這個取代了什麼功能

優化了原本維護 pivot table 的複雜程度

原先 pivot table 維護方式

  1. 建立 orders products order_products 三個資料表

  2. 建立 Order Product OrderProduct 三個 model

  3. 建立新訂單時,需要

    $data = $request->data;
    $items = $data['products'] // [1, 2, 3, ...] product_ids
    
    // 建立訂單
    $newOrder = Order::create($data['meta']);
    
    // 建立訂單與商品的關聯
    foreach ($items as $item) {
      OrderProduct::create(['order_id' => $newOrder->id, 'product_id' => $item]);
    }
    
  4. 更新訂單時,需要 (最麻煩部分)

    $order = Order::find($id);
    
    $oldProductIds = $order->products->pluck('id'); // [1, 2, 3]
    $newProductIds = $request['data']['products']; // [3, 4, 5]
    
    // find and destroy old items needed to be removed
    $removeIds = array_filter(function ($item) use ($oldProductIds, $newProductIds) {
    	return  !in_array($newProductIds, $item);
    }, $oldProductIds);
    
    OrderProduct::where('order_id', $id)->whereIn('product_id', $removeIds)->delete();
    
    // create new items
    foreach ($newProductIds as $item) {
    	OrderProduct::firstOrCreate(['order_id' => $id, 'product_id' => $item]);
    }
    

使用 sync 後的優化方式

  1. 建立 orders products order_product 三個資料表

    1. 注意:這種多對多關聯用的中間表,叫做 Pivot table
    2. 注意:不要在 order_product 中使用 softDeletes()
    3. 注意:order_product 資料表名為固定,只能用單數,不能複數
    Schema::create('order_product', function (Blueprint $table) {
    	  $table->id();
    	  $table->unsignedBigInteger('order_id');
    	  $table->unsignedBigInteger('product_id');
    	  $table->integer('index');  // 商品在訂單中的順序
    });
    
  2. 建立 Order Product 兩個 model

    class Order extends Model
    {
      public function products()
      {
    	  return $this->hasMany(Product::class)->withPivot('index');
      }
    }
    
    class Product extends Model
    {
      public function orders()
      {
    	  return $this->belongsToMany(Order::class);
      }
    }
    
  3. 建立訂單

    $order = Order::create($request['data']['meta']);
    
    $products = $request['data']['products']; 
    /*
    [
      [10 => ['index' => 1]],
      [15 => ['index' => 2]],
      [11 => ['index' => 3]],
    ]
    */
    
    $order->products()->**attach**($products);
    
  4. 更新訂單 (最複雜變成最簡單~)

    $order = Order::create($request['data']['meta']);
    $products = $request['data']['products'];
    
    // method 1
    /*
    	$products = [
    	  [5 => ['index' => 1]],
    	  [7 => ['index' => 2]],
    	  [9 => ['index' => 3]],
    	]
    */
    $order->products()->**sync**($products);
    
    // method 2
    /*
      $products = [5, 7, 9] 只有 primary ids
    */
    $order->products()->sync($products);
    
    // method3
    /*
      $products = [5, 7, 9] 只有 primary ids
      但同時需要更新 "更新時間"
    */
    $order->products()->syncWithPivotValues($products, ['updated_at', new Carbon::()]);