PHP

Adding Items to the Shopping Cart

Example 11-3 shows the cart.3 script, which adds items to the shopping cart. The script expects two parameters: a wineId that matches a wine_id in the wine table and a qty (quantity) of the wine to add to the cart. These parameters are supplied by clicking on embedded links on the home or search pages. For example, the home page contains links such as:

<a href="example.cart.3.php?qty=1&amp;wineId=624">
Add a bottle to the cart</a>

When the user clicks on the link, the cart.3 script adds a bottle of the wine to the cart, database processing occurs, and the user is redirected back to the calling page. This use of one-component querying for adding wines to the cart is discussed in more detail in Chapter 5.

cart.3 has several steps:

  1. It checks whether the shopping cart exists. If it does exist, it locks the items table for writing and the inventory table for reading. If the cart doesn't exist, the orders table is also locked for writing.

  2. Locking is required since the script may suffer from the dirty read and lost update concurrency problems discussed in Chapter 6. These problems can occur if another user is simultaneously creating a shopping cart-without locking, both users may obtain the same cart number-or if an inventory is sold out while an item is being added to the cart, in which case the item price in the cart may be wrong.

  3. After locking the required tables, the script tests whether a cart already exists. If it doesn't exist, it is created as a new row in the orders table with the next available order_id for the dummy customer. The order_id is then assigned to the session variable order_no. If the cart does exist, the script checks if the item being added to the cart is already one of the items in the cart. If it is, the item_id is saved so that the quantity of the item can be updated. If it isn't in the cart, a new item_id is assigned to the new wine.

    If this is a new item being added to the cart, the script queries to find the cheapest inventory price for the wine. An error is reported if the wine has sold out by registering a message as a session variable; messages are displayed by all scripts that interact with the user through a call to the showMessage( ) function incorporated in include.inc. Wines selling out is an unusual occurrence: it occurs only if another user purchases all the remaining stock of a wine before this user clicks on the embedded link.

  4. After all checks of the cart and the inventory, the cart item is updated or inserted.

  5. The table locks are released.

  6. Finally, the script redirects to the calling page, completing the one-component add-to-cart script.

Example 11-3. cart.3 adds a quantity of a specific wine to the shopping cart
<?php
   // This script adds an item to the shopping cart
   // It expects a WineId of the item to add and a
   // quantity (qty) of the wine to be added
   include 'include.inc';
   set_error_handler("errorHandler");
   // Have the correct parameters been provided?
   if (empty($wineId) && empty($qty))
   {
      session_register("message");
      $message =
        "Incorrect parameters to example.cart.3.php";
      // Redirect the browser back to the calling page
      header("Location: $HTTP_REFERER");
      exit;
   }
   // Re-establish the existing session
   session_start(  );
   $wineId = clean($wineId, 5);
   $qty = clean($qty, 3);
   $update = false;
   // Open a connection to the DBMS
   if (!($connection = @ mysql_pconnect($hostName,
                                        $username,
                                        $password)))
      showerror(  );
   if (!mysql_select_db($databaseName, $connection))
      showerror(  );
   // If the user has added items to their cart, then
   // the variable order_no will be registered
   // First, decide on which tables to lock
   // We don't touch orders if the cart already exists
   if (session_is_registered("order_no"))
      $query = "LOCK TABLES inventory READ, items WRITE";
   else
      $query = "LOCK TABLES inventory READ,
                            orders WRITE,
                            items WRITE";
   // LOCK the tables
   if (!(@ mysql_query ($query, $connection)))
      showerror(  );
   // Second, create a cart if we don't have one yet
   // or investigate the cart if we do
   if (!session_is_registered("order_no"))
   {
      // Find out the maximum order_id, then
      // register a session variable for the new order_id
      // A cart is an order for the customer
      // with cust_id = -1
      $query = "SELECT max(order_id) FROM orders
                WHERE cust_id = -1";
      if (!($result = @ mysql_query ($query, $connection)))
         showerror(  );
      // Save the cart number as order_no
      // This is used in all cart scripts to access the cart
      session_register("order_no");
      $row = @ mysql_fetch_array($result);
      $order_no = $row["max(order_id)"] + 1;
      // Now, create the shopping cart
      $query = "INSERT INTO orders
                SET cust_id = -1,
                    order_id = $order_no";
      if (!(@ mysql_query ($query, $connection)))
         showerror(  );
      // Default the item_id to 1
      $item_id = 1;
   }
   else
   {
      // We already have a cart.
      // Check if the customer already has this item
      // in their cart
      $query = "SELECT item_id, qty FROM items
                WHERE cust_id = -1
                AND order_id = $order_no
                AND wine_id = $wineId";
      if (!($result = @ mysql_query ($query, $connection)))
         showerror(  );
      // Is the item in the cart already?
      if (mysql_num_rows($result) > 0)
      {
         $update = true;
         $row = @ mysql_fetch_array($result);
         // Save the item number
         $item_id = $row["item_id"];
      }
      // If this is not an update, find the
      // next available item_id
      if ($update == false)
      {
         // We already have a cart, find the maximum item_id
         $query = "SELECT max(item_id) FROM items
                   WHERE cust_id = -1
                   AND order_id = $order_no";
         if (!($result = @ mysql_query ($query,
                                        $connection)))
            showerror(  );
         $row = @ mysql_fetch_array($result);
         // Save the item number of the new item
         $item_id = $row["max(item_id)"] + 1;
      }
   }
   // Third, add the item to the cart or update the cart
   if ($update == false)
   {
      // Get the cost of the wine
      // The cost comes from the cheapest inventory
      $query = "SELECT count(*), min(cost) FROM inventory
                WHERE wine_id = $wineId";
      if (!($result = @ mysql_query ($query, $connection)))
         showerror(  );
      $row = @ mysql_fetch_array($result);
      // This wine could have just sold out - check this
      // (this happens if another user buys the last bottle
      //  before this user clicks "add to cart")
      if ($row["count(*)"] == 0)
      {
         // Register the error as a session variable
         // This message will then be displayed back on
         // page where the user adds wines to their cart
         session_register("message");
         $message =
            "Sorry! We just sold out of this great wine!";
      }
      else
      {
         // We still have some of this wine, so save the
         // cheapest available price
         $cost = $row["min(cost)"];
         $query = "INSERT INTO items
                   SET cust_id = -1,
                       order_id = $order_no,
                       item_id = $item_id,
                       wine_id = $wineId,
                       qty = $qty,
                       price = $cost";
      }
   }
   else
      $query = "UPDATE items
                SET qty = qty + $qty
                WHERE cust_id = -1
                AND order_id = $order_no
                AND item_id = $item_id";
   // Either UPDATE or INSERT the item
   // (Only do this if there wasn't an error)
   if (empty($message) &&
       (!(@ mysql_query ($query, $connection))))
     showerror(  );
   // Last, UNLOCK the tables
   $query = "UNLOCK TABLES";
   if (!(@ mysql_query ($query, $connection)))
     showerror(  );
   // Redirect the browser back to the calling page,
   // using the HTTP response header "Location:"
   // and the PHP environment variable $HTTP_REFERER
   header("Location: $HTTP_REFERER");
?>