首页 > 解决方案 > 我应该使用什么样的连接,或者我应该为此制作模型?

问题描述

我有 3 张桌子-carts和.cart_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 外观?

任何帮助将非常感激。

标签: phpsqllaraveljoineloquent

解决方案


您应该使用模型,因为您可以预先加载产品并保存一些查询(稍后阅读更多内容),并且通过使用模型,您可以利用 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. 编辑 Product 资源以按照您想要的方式显示输出:
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 资源的文档,并此处查看有关 eager-loading的文档


推荐阅读