Skip to content

Lab 12: Session-Based Shopping Cart

In this lab, you will build a session-based shopping cart for the customer-facing side of your e-commerce application. The cart stores items in $_SESSION using the SessionManager helper you built in Lab 3, so no new database tables are needed. You will integrate with the existing ProductsModel to look up product details when items are added, then cache those details in the session so the cart view requires no additional database queries.

The four core operations mirror the CRUD pattern from previous labs: Add (create a cart entry), View (read and display cart contents), Update Quantity (modify an existing entry), and Remove (delete a cart entry).


By completing this lab, you will:

  • Use $_SESSION as a key-value store to persist cart data across HTTP requests.
  • Design a cart data structure that caches product details alongside quantity.
  • Build a CartController that injects and reuses an existing model class (ProductsModel) without creating a new model.
  • Apply the Post-Redirect-Get (PRG) pattern across all cart-mutating operations.
  • Embed POST forms in an existing public view to trigger state-changing controller actions.
  • Display flash messages in the public layout to provide user feedback after cart operations.

Before starting this lab, ensure you have:

  • Completed Lab 3 (SessionManager and SessionMiddleware).
  • Completed Lab 4 (FlashMessage helper class).
  • Completed Labs 5–6 (ProductsModel with getAll() and findById() methods).
  • Completed Lab 8 (public product listing view with product cards).
  • The products table in your database with sample data.

Before writing any code, understand how the cart will be stored in the session. The cart is an associative array stored under the key 'cart', where each entry is keyed by the product ID:

// What the cart looks like in $_SESSION
$_SESSION['cart'] = [
'3' => ['product_id' => 3, 'name' => 'Widget Pro', 'price' => 29.99, 'quantity' => 2],
'17' => ['product_id' => 17, 'name' => 'Gadget X', 'price' => 9.99, 'quantity' => 1],
];

Why cache name and price? When a product is added to the cart, you fetch its details from the database once and store them alongside the quantity. This way, the cart view can display product names and prices without querying the database on every page load.

Why key by product ID? Using the product ID as the array key makes it trivial to check if a product is already in the cart, update its quantity, or remove it, all in O(1) time instead of looping through the array.


Objective: Define all the routes needed for cart operations.

Instructions:

  1. Open app/Routes/web-routes.php
  2. Add the following import at the top of the file:
use App\Controllers\CartController;
  1. Register the following five routes outside the /admin group (these are customer-facing routes):
HTTP MethodURIController MethodRoute Name
GET/cartindexcart.index
POST/cart/addaddcart.add
POST/cart/updateupdatecart.update
POST/cart/removeremovecart.remove
POST/cart/clearclearcart.clear
  1. Save the file

Objective: Build a controller that manages all cart operations using session storage.

Instructions:

  1. Navigate to app/Controllers/

  2. Create a new file named CartController.php

  3. Define the CartController class following the same pattern as ProductsController:

    • Extend BaseController and inject Container and ProductsModel in the constructor (the same pattern you used in previous labs)
    • Refer to Creating a Controller Class if you need a refresher on the controller structure
  4. Implement the following five methods:

This method reads the cart from the session and renders the cart view.

  • Read the cart from the session using SessionManager::get('cart', []). The second argument [] ensures you get an empty array if no cart exists yet.
  • Calculate the total number of items by summing the quantity of every entry in the cart.
  • Calculate the grand total price by summing price * quantity for every entry.
  • Pass the cart array, item count, and grand total to the view 'cart/cartIndexView.php' via $this->render().

Method 2: add() → Add a Product to the Cart

Section titled “Method 2: add() → Add a Product to the Cart”

This method handles the POST request when a user clicks “Add to Cart”.

  • Extract product_id from $request->getParsedBody() and cast it to int.
  • Validate that the product ID is greater than 0. If not, set an error flash message and redirect to cart.index.
  • Fetch the product from the database using $this->productsModel->findById($productId). If the product is not found (returns false), set an error flash message and redirect.
  • Read the current cart from the session.
  • If the product already exists in the cart, increment its quantity by 1.
  • If the product does not exist in the cart, add a new entry with the product’s id, name, price (fetched from the database), and a quantity of 1.
  • Write the updated cart back to the session using SessionManager::set('cart', $cart).
  • Set a success flash message (e.g., "'{product name}' was added to your cart.").
  • Redirect to cart.index.

Method 3: update() → Update Item Quantity

Section titled “Method 3: update() → Update Item Quantity”

This method handles the POST request when a user changes the quantity of an item.

  • Extract product_id and quantity from $request->getParsedBody(). Cast both to int.
  • Read the current cart from the session.
  • If the product is not in the cart, set an error flash message and redirect.
  • If the new quantity is less than or equal to 0, remove the item from the cart (use unset()) and set an info/success flash message indicating the item was removed.
  • Otherwise, update the quantity for that product and set a success flash message.
  • Write the updated cart back to the session.
  • Redirect to cart.index.

This method handles the POST request when a user clicks “Remove” on a cart item.

  • Extract product_id from $request->getParsedBody() and cast it to int.
  • Read the current cart from the session.
  • If the product exists in the cart, unset() that entry, write the cart back, and set a success flash message.
  • If the product is not in the cart, set an error flash message.
  • Redirect to cart.index.

Method 5: clear() → Empty the Entire Cart

Section titled “Method 5: clear() → Empty the Entire Cart”

This method removes all items from the cart at once.

  • Call SessionManager::remove('cart') to delete the entire cart from the session.
  • Set a success flash message (e.g., “Your cart has been cleared.”).
  • Redirect to cart.index.
  1. Save the file

Objective: Build a view that displays the cart contents with controls to update quantities and remove items.

Instructions:

  1. Create a new directory app/Views/cart/ (if it does not exist)
  2. Create a new file named cartIndexView.php inside that directory
  3. Build the view with the following structure:

Layout:

  • Use the public layout: call ViewHelper::loadHeader('Shopping Cart') at the top and ViewHelper::loadFooter() at the bottom.

Empty cart state:

  • If the cart is empty, display a Bootstrap info alert with the message “Your cart is empty.” and include a link back to the product listing page (/products).

Cart table (when items exist):

  • Render a Bootstrap table (table table-striped table-hover) with the following columns:
ColumnContent
Product NameThe product name (escaped with hs())
Unit PriceThe price formatted with number_format($item['price'], 2)
QuantityA number <input> inside a small form (see below)
SubtotalCalculated as price * quantity, formatted with number_format()
ActionsUpdate and Remove buttons (see below)

Quantity update form:

  • In each row, the Quantity column and the “Update” button should be wrapped in a <form> that POSTs to the update route (APP_BASE_URL . '/cart/update').
  • Include a hidden input for product_id.
  • Include a number input for quantity with min="0" and the current quantity as the default value.
  • Include a submit button labeled “Update”.

Remove form:

  • In each row, the Actions column should also contain a separate <form> that POSTs to the remove route (APP_BASE_URL . '/cart/remove').
  • Include a hidden input for product_id.
  • Include a submit button labeled “Remove” (styled as a danger/red button).

Cart summary:

  • Below the table, display a row showing the grand total (passed from the controller).

Action buttons below the table:

  • A “Clear Cart” form that POSTs to the clear route (APP_BASE_URL . '/cart/clear').
  • A “Continue Shopping” link that navigates to the product listing page.
  1. Save the file

Step 4: Add “Add to Cart” Button to the Product Listing

Section titled “Step 4: Add “Add to Cart” Button to the Product Listing”

Objective: Integrate an “Add to Cart” button into the existing public product cards.

Instructions:

  1. Open app/Views/products/userProductIndexView.php

  2. Locate the card-footer section inside the product card loop (the #defaultProducts div)

  3. Add a small POST form next to the existing “View Details” link. The form should:

    • Set the method to POST
    • Set the action to the add-to-cart route (APP_BASE_URL . '/cart/add')
    • Contain a hidden input named product_id with the product’s id as the value
    • Contain a submit button labeled “Add to Cart” (styled as a secondary/outline button to distinguish it from “View Details”)
  4. Save the file


Section titled “Step 5: Add Cart Link to the Public Navigation”

Objective: Add a cart link with item count badge to the public navigation bar.

Instructions:

  1. Open your public header view (app/Views/common/header.php or equivalent)

  2. Add a “Cart” link in the navigation bar that points to the cart page (APP_BASE_URL . '/cart')

  3. Display the total number of items in the cart as a Bootstrap badge next to the link:

    • Read the cart from the session using SessionManager::get('cart', []) (use the fully qualified class name \App\Helpers\SessionManager since this is a view file without namespace imports).
    • Loop through the cart array and sum up the quantity values to get the total item count.
    • Render the cart link as a navigation item. If the item count is greater than 0, display it inside a Bootstrap badge (<span class="badge bg-danger">) next to the link text.
  4. While you are editing the public header, verify that FlashMessage::render() is present somewhere in the layout so that flash messages display after cart operations. If it is not already there, add a call to \App\Helpers\FlashMessage::render() inside a container div in the main content area.

  5. Save the file


Objective: Verify that all cart operations work correctly, including edge cases.

Instructions:

Test each of the following scenarios in your browser:

  1. Add a product: Click “Add to Cart” on the product listing page. Verify you are redirected to the cart page and see a success flash message. Confirm the product appears in the cart table with a quantity of 1.

  2. Add the same product again: Go back to the product listing and click “Add to Cart” on the same product. Verify the quantity increments to 2 (not a duplicate entry).

  3. Update quantity: Change the quantity input to 5 and click “Update”. Verify the quantity and subtotal update correctly.

  4. Set quantity to 0: Change the quantity to 0 and click “Update”. Verify the item is removed from the cart.

  5. Set a negative quantity: Change the quantity to a negative number and click “Update”. Verify it is treated the same as 0 (item removed).

  6. Remove an item: Add a product and click “Remove”. Verify the item is removed and a success flash message appears.

  7. Empty cart state: Remove all items from the cart. Verify the “Your cart is empty” message displays with a link back to products.

  8. Clear cart: Add multiple products, then click “Clear Cart”. Verify all items are removed at once.

  9. Fresh session: Open the cart page in a private/incognito window (no existing session). Verify it shows the empty cart state without any errors.


Section titled “Step 7: (Optional) Add Cart Link to Admin Sidebar”

Objective: For convenience during development, add a link in the admin sidebar to quickly view the storefront cart.

Instructions:

  1. Open app/Views/admin/adminHeader.php
  2. Add a “View Storefront Cart” link in the sidebar navigation that points to /cart
  3. Save the file

This is useful during development because you can manage products in the admin panel and quickly switch to the customer-facing cart to test. Both the admin panel and the storefront share the same session, so items added from the public product listing are visible from any URL.