【问题标题】:What kind of join should I use or should I make models for that?我应该使用什么样的连接,或者我应该为此制作模型?
【发布时间】:2019-12-14 03:21:27
【问题描述】:

我有 3 张桌子 - cartscart_productsproducts

它们的结构如下所示:

手推车:

id, employee_id, name, paid

购物车产品:

id, cart_id, product_id, amount

产品:

id, name, price

现在在我的代码中,我抓取了所有未付费的购物车:

$carts = Cart::where('paid', false)->select('id')->get();

并遍历它们:

foreach ($carts as $key) {
    $cart_products = DB::table('cart_products')
    ->where('cart_id', $key->id)
    ->get();

    $returnedCarts[] = [
        'id' => $key->id,
        'name' => $key->name,
        'products'=> $cart_products      
    ];
}

return response()->json($returnedCarts);

现在我的问题是如何更改包含 JSON 的产品:

{
    "id": 1,
    "name": null,
    "products": [ //this is the cart_products table
        {
            "id": 1,
            "cart_id": 1,
            "product_id": 1,
            "amount": 2,
            "created_at": null,
            "updated_at": null
        }
    ]
},

进入这个:

{
    "id": 1,
    "name": null,
    "products": [ //this is data from the products table
        {
            "product_id": 1,
            "amount": 2, //but this is from the cart_products table
            "name": "This product name",
            "price" "$9,49"
        }
    ]
},

在 foreach 循环中无需额外查询。我应该使用什么样的联接?我应该更改我的代码还是应该使用模型而不是 DB 外观?

任何帮助将不胜感激。

【问题讨论】:

    标签: php sql laravel join eloquent


    【解决方案1】:

    您可以使用 Laravel Eloquent、RelationshipsEager Loading

    首先,定义模型中的关系:

    # Cart.php
    
    use App\Product;
    
    // ...
    
    public function products()
    {
        return $this->belongsToMany(Product::class)->withPivot('amount');
    } //             ^^^^^^^^^^^^^^^relationship    ^^^^^^^^^aditional intermediate columns
    

    然后,在您的控制器中,您可以:

    # CartsController.php
    
    use App\cart;
    
    // ...
    
    public function yourFunction()
    {
        $carts = Cart
             ::with('products') // <-- Eager loading the relationship 'products'
             ->where('paid', false) // <-- constraining the query
             ->get();  // <-- executing the query.
    
        return $carts;
    }
    

    这会给你类似的东西:

    {
        "id": 1,
        "name": "Cart name",
        "products": [
            {
                "id": 1,
                "name": "This product name",
                "price" "$9,49",
                "pivot": {   // <-- notice this
                     "amount": 2
                 },
            }
        ]
    },
    

    【讨论】:

      【解决方案2】:

      您应该使用模型,因为您可以预先加载产品并节省一些查询(稍后阅读更多内容),并且通过使用模型,您可以利用 Eloquent API 资源来更好地控制输出(哪些字段,以什么顺序,在哪里获取它们等)。

      N+1 查询问题

      您现在正遭受 N+1 查询问题,因为您获得了所有 N 个未付费购物车(1 个查询),并且每个 其中您获得了他们的产品(N 个查询,每个购物车一个)。

      实现模型关系

      在购物车模型中,您可以像这样设置与产品的关系:

      public function products()
      {
          return $this->belongsToMany('App\Product')->withPivot('amount');
      }
      

      您还可以添加一个查询范围,以简单地将告诉您购物车未付款的 where 条件保留在模型中而不是控制器中(稍后您将了解如何使用它):

      public function scopeUnpaid($query)
      {
          return $query->where('paid', false);
      }
      

      API 资源

      要实现 Eloquent API 资源,您必须:

      1. 使用 artisan 为 Cart 和 Product 创建 API 资源类:
      php artisan make:resource Cart
      
      php artisan make:resource Product
      

      这些命令将创建两个文件app/Http/Resources/Cart.phpapp/Http/Resources/Product.php

      1. 编辑购物车资源以在输出中显示您想要的字段,否则将返回所有购物车字段:
      // Do not forget the use statement at the top
      use App\Http\Resources\Product as ProductResource;
      
      // Then replace the toArray method
      public function toArray($request)
      {
          // $this refers to the current Cart instance
          return [
              'id' => $this->id,
              'name' => $this->name,
              // This line tells to render each product of this cart with the Product API Resource,
              // in this way you can also control how each product model will be displayed
              // in the json response
              'products' => ProductResource::collection($this->products)
          ];
      }
      
      1. 编辑产品资源以按照您想要的方式显示输出:
      public function toArray($request)
      {
          // $this refers to the current Product instance
          // As you requested, here you can set the field and it's value.
          return [
              'product_id' => $this->id,
              // this field is taken from the cart_product table, that is loaded
              // as you specified to load the amount attribute with the ->withPivot('amount')
              // instruction in your products() relation
              'amount' => $this->pivot->amount,
              'name' => $this->name,
              'price' => $this->price
          ];
      }
      

      在控制器中,您现在可以预先加载未付费购物车的所有产品,并使用 API 资源编辑将作为响应发送的内容,以仅显示您需要的属性。

      // Do not forget to import the Cart model and the Cart Resource at the top
      use App\Cart;
      use App\Http\Resources\Cart as CartResource;
      
      public function your_method()
      {
          // You can use the unpaid() query scope you added earlier
          // as a simple shorthand to ->where('paid', false') function
          // on the query builder.
          // ->with('products') will eager-load the products relationship
          // and therefore retrive the products associated to the carts you
          // are gonna retrive with just one additional query, not one for each cart.
          $carts = Cart::unpaid()->with('products')->get();
      
          return CartResource::collection($carts);
      }
      

      您可以查看有关 API 资源 here 和急切加载 here 的文档

      【讨论】:

      • 我喜欢你的回答,详细解释+1。
      • 感谢您提供的所有详细信息,我学到了一些新东西。为您 +1,但是我将简化的帖子标记为答案,但恕我直言,这应该保持不变。
      猜你喜欢
      • 1970-01-01
      • 2017-05-27
      • 2017-03-17
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 2019-11-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多