Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ralphmorris's avatar

How to structure Basket/Cart functionality

Hey all!

I'm building Cart functionality into an existing Laravel application. The shop has stock, coupons etc. I started with storing the Cart in the session which works well, but am considering moving it to the DB for a couple of reasons. I'm interested to know what others think and if there are any better alternatives.

Stock reservation

As we have stock and we cannot sell more than we have, we need a stock reservation feature. Building this into the session seems more complex than just having Cart items in the DB, which can then be auto deleted by a cron job daily or whenever it should expire.

Coupon restrictions

Similar to stock reservations, coupons have limits to how many times they can be used. During the applyCoupon method we need to validate that it has been used less times than the max. But when the max is 10, should we allow it to be added to a Cart unlimited times until the number of orders using the coupon reaches 10? Or have a reservation feature on coupons too. eg. Apply it to your Cart and reserve it, but if your Cart expires unreserve it.

I could create a reservations table to handle the above in conjunction with session management, but if I'm going to put that in the table anyway it feels like I'm duplicating effort and may as well create Cart/CartItem model which would be very similar.

I'm even tempted to just create the Orders table that has a status of 'cart' so that I don't eventually have to convert it to an order schema anyway.

If it was in the DB, it would still be anonymous whilst it was a Cart and not an order so privacy isn't a concern. Carts older than say, 1 day, would be deleted.

Interest in any feedback. Cheers!

0 likes
4 replies
LaryAI's avatar
Level 58

When implementing a cart system in a Laravel application, especially with considerations like stock reservation and coupon restrictions, it's important to choose a structure that balances performance, scalability, and complexity. Here's a suggested approach:

Database Structure

  1. Cart Table: Store basic cart information, such as user ID (nullable for guest carts), session ID, and timestamps.

  2. CartItems Table: Store each item in the cart, including product ID, quantity, and any other relevant details like price at the time of adding to the cart.

  3. Reservations Table: Handle stock and coupon reservations. This table can track reserved quantities and coupon usage.

  4. Orders Table: Use this table to transition a cart to an order. It can have a status field to differentiate between 'cart' and 'completed' orders.

Stock Reservation

  • Reservation Logic: When an item is added to the cart, create a reservation entry. This can be a simple record in the reservations table with a reference to the cart item and the quantity reserved.

  • Expiration: Use a scheduled task (cron job) to periodically clean up expired reservations and carts. This ensures that stock is not held indefinitely.

Coupon Restrictions

  • Apply and Reserve: When a coupon is applied, check its usage against the limit. If valid, create a reservation entry for the coupon.

  • Release on Expiry: Similar to stock, ensure that coupon reservations are released if the cart expires or is abandoned.

Implementation Example

Here's a basic example of how you might structure the models and relationships:

Considerations

  • Anonymous Carts: Use session IDs to track carts for non-logged-in users. This allows you to maintain cart functionality without requiring user accounts.

  • Performance: Storing carts in the database can be more performant for larger applications, especially when dealing with stock and coupon reservations.

  • Security and Privacy: Ensure that sensitive data is not stored in the cart tables, especially if they are accessible to anonymous users.

  • Scalability: This structure allows you to easily scale your application, as database operations can be optimized and distributed.

By using a database-centric approach, you gain more control over the cart lifecycle, stock management, and coupon usage, while also preparing for future scalability and feature expansion.

Snapey's avatar
Snapey
Best Answer
Level 122

You could have draft status on carts so that you can check available stock plus stock held in draft orders? Your stock could be reserved by a 'held_until' timestamp on your order_items table then when counting held stock you can automatically ignore any held stock that has passed the held until time, even if the draft order has not yet been cleaned up by cron task.

For coupons, I normally apply these in the checkout so that there should be minimal time between checking for coupon availability and coupon use.

1 like
ralphmorris's avatar

Thanks @snapey,

That sounds like more or less what I was thinking. Putting it in the database feels so much simpler than in the session, when it comes to handling these restrictions.

I like the 'draft' status rather than 'cart;'. held_until sounds good too. Then an easy clean up cron job.

martinbean's avatar

@ralphmorris Unfortunately, these are business logic questions that you need to answer, rather than programming problems that have one canonical answer. Where stock gets reserved and depleted (i.e. when a user adds an item to their cart versus when a pays for an order) is something you’ll need to decide based on what makes sense for your business and application, and code the solution accordingly.

That being said, you’re right in that carts are something that are better modelled and stored in the database. You can then do much more business logic versus just keeping an array of items and corresponding quantities in a session variable. I tend to have cart and cart item models, and then code that converts these to orders and order items when a customer completes checkout and payment.

For inventory, there are a couple of ways you can model this. You can either use a “ledger” approach and record individual stock level adjustments. When stock comes in, you insert a row with the quantity. When a customer adds something to their cart, you insert a row with a negative quantity. The amount in stock is then just a sum of these quantities. If a cart expires, the corresponding stock level adjustment(s) are deleted, and that quantity goes back in stock.

Alternatively, you could just have inventory objects with an in_stock value, and create a separate model for reservations (i.e. when a customer adds an item to their cart). But you then need to ensure modifications to these two tables are done in a transaction—you need to ensure the in_stock value is decremented to the same time you create a reservation, and also that the in_stock value is incremented when an adjustment is deleted (i.e. a cart expires):

public function reserveStock(CartItem $cartItem)
{
    DB::transaction(function () use ($cartItem) {
        $cartItem->product->inventory->decrement('in_stock', $cartItem->quantity);

        $cartItem->product->inventory->reservations()->create([
            'quantity' => $cartItem->quantity,
        ]);
    });
}

This approach can also be a pain as you need to constantly update in_stock and reservations when a customer say, changes the quantity of a particular item in their cart. It can also lead to inconsistencies if a row in your inventory_reservations table is deleted by some process or code that doesn’t also update the corresponding inventory’s in_stock value (i.e. a scheduled task, or a raw DB::table('inventory_reservations')->delete($id) method call).

Please or to participate in this conversation.