Home > Back-end >  Laravel API: Load nested relationship in resource
Laravel API: Load nested relationship in resource

Time:11-06

I've got 3 models: Order, OrderItem and Product (for simplicity just showing the relationships):

class Order extends BaseModel
{
    use Uuid;

    protected $casts = [
        'status' => OrderStatuses::class,
    ];

    /**
     * An Order has multiple OrderItems associated to it.
     * @return HasMany
     */
    public function orderItems(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }
class OrderItem extends BaseModel
{
    /**
     * Get the Order the OrderItem belongs to.
     * @return BelongsTo
     */
    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class)
                ->withDefault();
    }

    /**
     * Get the product associated to this OrderItem.
     * @return HasOne
     */
    public function product(): HasOne
    {
        return $this->hasOne(Product::class, 'id');
    }
}
class Product extends BaseModel
{
    use Uuid;

    /**
     * Get the category the product belongs to.
     * @return BelongsTo
     */
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'category_id');
    }

with their respective DB tables:

orders: id, status, subtotal, total 
order_items: id, order_id, product_id, qty, price, total
products: id, name, slug, sku, description, price

I've got only a controller for Order and Product but I do have resources for all 3:

class OrdersResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => (string)$this->id,
            'type' => 'orders',
            'attributes' => [
                'status' => ($this->status)->value(),
                'payment_type' => $this->payment_type,
                'payment_transaction_no' => $this->payment_transaction_no,
                'subtotal' => $this->subtotal,
                'taxes'  => $this->taxes,
                'total' => $this->total,
                'items' => OrderItemsResource::collection($this->whenLoaded('orderItems')),
                'created_at' => $this->created_at,
                'updated_at' => $this->updated_at,
            ]
        ];
    }
}
class ProductsResource extends JsonResource
{
    public function toArray($request) : array
    {
        return [
            'id' => $this->id,
            'type' => 'products',
            'attributes' => [
                'barcode' => $this->barcode,
                'name' => $this->name,
                'slug' => $this->slug,
                'sku' => $this->sku,
                'description' => $this->description,
                'type' => $this->type,
                // todo return  category object?
                'category' => new CategoriesResource($this->whenLoaded('category')),
                'wholesale_price' => $this->wholesale_price,
                'retail_price' => $this->retail_price,
                'base_picture' => ($this->base_picture ? asset('images/products/' . $this->base_picture) : null),
                'current_stock_level' => $this->current_stock_level,
                'active' => $this->active,
            ]
        ];
    }
}
class OrderItemsResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'order_id' => $this->order_id,
            'product_id' =>  $this->product_id,
            'qty' => $this->qty,
            'price' => $this->price,
            'total' => $this->total,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

When hitting my orders controller I return the data (in this case for displaying an order) like this:

public function show(Order $order): JsonResponse
    {
        return (new OrdersResource($order->loadMissing('orderItems')))
            ->response()
            ->setStatusCode(Response::HTTP_OK);
    }

So far so go, the order is returned like this:

{
    "data": {
        "id": "20d9b0f3-2b32-45a7-8814-12c77210ad50",
        "type": "orders",
        "attributes": {
            "status": "new",
            "payment_type": "",
            "payment_transaction_no": "",
            "subtotal": 71000,
            "taxes": 0,
            "total": 71000,
            "items": [
                {
                    "id": 9,
                    "product_id": "444b0f3-2b12-45ab-3434-4453121231ad51",
                    "order_id": "20d9b0f3-2b32-45a7-8814-12c77210ad50",
                    "qty": 10,
                    "price": 200,
                    "total": 2000,
                    "created_at": "2022-11-05T16:26:07.000000Z",
                    "updated_at": "2022-11-05T16:28:02.000000Z"
                },
                {
                    "id": 10,
                    "product_id": "324b0f3-2b12-45ab-3434-12312330ad50",
                    "order_id": "20d9b0f3-2b32-45a7-8814-12c77210ad50",
                    "qty": 3,
                    "price": 23000,
                    "total": 69000,
                    "created_at": "2022-11-05T16:26:29.000000Z",
                    "updated_at": "2022-11-05T16:26:29.000000Z"
                }
            ],
            "created_at": "2022-11-05T16:26:07.000000Z",
            "updated_at": "2022-11-05T16:28:02.000000Z"
        }
    }
}

but now I'm in need of returning the actual product as part of OrderItem instead of just the product ID, so I updated my resource to include: 'product' => new ProductsResource($this->whenLoaded('product')),

my resource ended up looking like this:

public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'order_id' => $this->order_id,
            'product' => new ProductsResource($this->whenLoaded('product')),
            'qty' => $this->qty,
            'price' => $this->price,
            'total' => $this->total,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }

yet the product is not visible in my response, what am I missing? Isn't that the right way to load it? do i need to load it from my controller?

Thanks

CodePudding user response:

In your controller:

$order->loadMissing('orderItems.product')

CodePudding user response:

Your Product model is missing the relationship to OrderItem. You defined:

// In OrderItem model
public function product(): HasOne
{
    return $this->hasOne(Product::class, 'id');
}

And you are missing the required inverse relationship:

// In Product model
public function orderItem(): BelongsTo
{
    return $this->belongsTo(OrderItem::class);
}
  • Related