Lab 12: Session-Based Shopping Cart
Additional Resources
Section titled “Additional Resources”- PHP Sessions ↗
- Working with the BaseController class
- Working with the BaseModel class
- Bootstrap Tables ↗
- Post-Redirect-Get Pattern ↗
Overview
Section titled “Overview”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).
Learning Objectives
Section titled “Learning Objectives”By completing this lab, you will:
- Use
$_SESSIONas a key-value store to persist cart data across HTTP requests. - Design a cart data structure that caches product details alongside quantity.
- Build a
CartControllerthat 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.
Prerequisites
Section titled “Prerequisites”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()andfindById()methods). - Completed Lab 8 (public product listing view with product cards).
- The
productstable in your database with sample data.
Cart Data Structure
Section titled “Cart Data Structure”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.
Lab Steps
Section titled “Lab Steps”Step 1: Register Cart Routes
Section titled “Step 1: Register Cart Routes”Objective: Define all the routes needed for cart operations.
Instructions:
- Open
app/Routes/web-routes.php - Add the following import at the top of the file:
use App\Controllers\CartController;- Register the following five routes outside the
/admingroup (these are customer-facing routes):
| HTTP Method | URI | Controller Method | Route Name |
|---|---|---|---|
| GET | /cart | index | cart.index |
| POST | /cart/add | add | cart.add |
| POST | /cart/update | update | cart.update |
| POST | /cart/remove | remove | cart.remove |
| POST | /cart/clear | clear | cart.clear |
- Save the file
Step 2: Create the CartController
Section titled “Step 2: Create the CartController”Objective: Build a controller that manages all cart operations using session storage.
Instructions:
-
Navigate to
app/Controllers/ -
Create a new file named
CartController.php -
Define the
CartControllerclass following the same pattern asProductsController:- Extend
BaseControllerand injectContainerandProductsModelin 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
- Extend
-
Implement the following five methods:
Method 1: index() → Display the Cart
Section titled “Method 1: index() → Display the Cart”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
quantityof every entry in the cart. - Calculate the grand total price by summing
price * quantityfor 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_idfrom$request->getParsedBody()and cast it toint. - 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 (returnsfalse), set an error flash message and redirect. - Read the current cart from the session.
- If the product already exists in the cart, increment its
quantityby 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 aquantityof 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_idandquantityfrom$request->getParsedBody(). Cast both toint. - 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.
Method 4: remove() → Remove an Item
Section titled “Method 4: remove() → Remove an Item”This method handles the POST request when a user clicks “Remove” on a cart item.
- Extract
product_idfrom$request->getParsedBody()and cast it toint. - 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.
- Save the file
Step 3: Create the Cart View
Section titled “Step 3: Create the Cart View”Objective: Build a view that displays the cart contents with controls to update quantities and remove items.
Instructions:
- Create a new directory
app/Views/cart/(if it does not exist) - Create a new file named
cartIndexView.phpinside that directory - Build the view with the following structure:
Layout:
- Use the public layout: call
ViewHelper::loadHeader('Shopping Cart')at the top andViewHelper::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:
| Column | Content |
|---|---|
| Product Name | The product name (escaped with hs()) |
| Unit Price | The price formatted with number_format($item['price'], 2) |
| Quantity | A number <input> inside a small form (see below) |
| Subtotal | Calculated as price * quantity, formatted with number_format() |
| Actions | Update 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
quantitywithmin="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.
- 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:
-
Open
app/Views/products/userProductIndexView.php -
Locate the
card-footersection inside the product card loop (the#defaultProductsdiv) -
Add a small POST form next to the existing “View Details” link. The form should:
- Set the
methodtoPOST - Set the
actionto the add-to-cart route (APP_BASE_URL . '/cart/add') - Contain a hidden input named
product_idwith the product’sidas the value - Contain a submit button labeled “Add to Cart” (styled as a secondary/outline button to distinguish it from “View Details”)
- Set the
-
Save the file
Step 5: Add Cart Link to the Public Navigation
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:
-
Open your public header view (
app/Views/common/header.phpor equivalent) -
Add a “Cart” link in the navigation bar that points to the cart page (
APP_BASE_URL . '/cart') -
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\SessionManagersince this is a view file without namespace imports). - Loop through the cart array and sum up the
quantityvalues 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.
- Read the cart from the session using
-
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. -
Save the file
Step 6: Test and Handle Edge Cases
Section titled “Step 6: Test and Handle Edge Cases”Objective: Verify that all cart operations work correctly, including edge cases.
Instructions:
Test each of the following scenarios in your browser:
-
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.
-
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).
-
Update quantity: Change the quantity input to 5 and click “Update”. Verify the quantity and subtotal update correctly.
-
Set quantity to 0: Change the quantity to 0 and click “Update”. Verify the item is removed from the cart.
-
Set a negative quantity: Change the quantity to a negative number and click “Update”. Verify it is treated the same as 0 (item removed).
-
Remove an item: Add a product and click “Remove”. Verify the item is removed and a success flash message appears.
-
Empty cart state: Remove all items from the cart. Verify the “Your cart is empty” message displays with a link back to products.
-
Clear cart: Add multiple products, then click “Clear Cart”. Verify all items are removed at once.
-
Fresh session: Open the cart page in a private/incognito window (no existing session). Verify it shows the empty cart state without any errors.
Step 7: (Optional) Add Cart Link to Admin Sidebar
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:
- Open
app/Views/admin/adminHeader.php - Add a “View Storefront Cart” link in the sidebar navigation that points to
/cart - 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.