Home Accessibility Courses Diary The Mouth Forum Resources Site Map About Us Contact
 
Python and Tcl - public course schedule [here]
Private courses on your site - see [here]
Please ask about maintenance training for Perl, PHP, Lua, etc
 
Shopping cart application in PHP

OVERVIEW

This module is a worked example - a simple shopping cart application which uses the power of PHP and its functions to provide session tracking for numerous visitors at the same time, data validation, templating of HTML to provide a consistent look and feel from page to page and the ability for that look and feel to be maintained through the use of an HTML editor such as Dreamweaver.

The application uses a single URL though which all the stages of the process are accessed. A session variable ($step) is used to track how far through the user has got, once it's established that a session is active.
 step 1 user is selecting products from the store
 step 2 user is entering his details (e.g. email address)
 step 3 user is entering secure details (e.g. credit card)
 step 4 order is logged. User is thanked.

Each of the batchlets that comprises the process is threaded into a single script, which goes through three application phases each time it is run:
 phase a handle incoming form information and decide if
   we can move on to the next step
 phase b general work such as reading the products
 phase c handling outgoing information - working out what
   has to go onto the output template

Within each phase, you'll see that we have a switch statement to select appropriate code; do note that the step variable changes BETWEEN the switch at phase a and the switch at phase c, which means that a user can enter his details on a form and have them received (step 2, phase a) and if they're valid the program will go on to step 3 phase c to generate a form asking for secure details.

THE STORE FRONT PAGE

Let's look at the from page of our store ...

Four elements are used in its creation:

i) The script carter.php4 which controls the whole operation ii) Functions in the file getshop.inc, which is included iii) HTML from a template file called offer.htp iv) Data about what the shop's called and what it offers - file products

Let's follow the creation of the front page:

[Update note. PHP now supports the $_SESSION superglobal variable which leads to slight coding changes. There's an example using $_SESSION at
/resources/ex.php4?item=h115/sc6.php
session_start();
include_once("getshop.inc");

if (! session_is_registered("cart")) {
        // New session - initialise for order entry
        session_register("cart","currentaisle","step","details");
        $cart = array(); # Current cart contents
  $details = array(email => "", postal =>"",
                name => "", card => ""); # Details of person
        $step = 1; # How far through?
                                        # 1 - selecting from aisles
                                        # 2 - giving own details
        $currentaisle = "";
}

It's a new session, so the shopping cart is created (empty!), an array for the user's details is also created with all its elements empty. We also make a note of the current aisle and step, as these are things that we wish to persist. We're not really into "step 1" yet while this code is being run, but never the less the common code is run:

//////////////////// ANY GENERAL WORK TO BE DONE

$fill = array_merge($details,$alert);
$fill[storename] = readproducts($products,$aisles,$currentaisle);
$fill[step] = $step;
$fill[totsteps] = 3;

This code starts to set up the "fill" array which is going to be used to complete the appropriate template just before the end of the batchlet; we actually know which template we'll be completing as our $step variable has been set, but some further information may yet have to be worked out.

Much of the work of phase b is encapsulated in (i.e. hidden within) our readproducts function; this function sets ups arrays of products and aisles from a data file, modifies the $currentaisle if necessary (it is necessary this first time through, as we put out newcomer into the entry aisle) and also returns the store title. Although functions usually take inputs from parameters and return values to be assigned to a variable, in this case we've broken from the norm and used parameters for return values; great way of dealing with a whole lot of variables to pass back, but do make it clear in your documentation!

Let's now move on and prepare to offer the products to the visitor:

$bask = "";
reset ($cart) ;
while ($item = each($cart)) {
        if ($item[value] > 0) {
                $haveitems = 1;
                $blines .= "<tr><td>".$products[$item[key]][title].
                        "</td><td>".
                        $item[value].
                        "</td><td>".
                        $products[$item[key]][days].
                        "</td><td>".
                        ($dc = $item[value]*$products[$item[key]][days]).
                        "</td></tr>";
                $totaldays += $dc;
        }
}
if ($blines) $bask = "<table border=1><tr><th>Product</th><th>places</th>".
                        "<th>duration</th><th>total days</th></tr>$blines</table
><br><br>";
$fill["bask"] = $bask;

OK - that was pretty pointless the first time through as we have nothing in the cart yet! The loop will have been skipped over completely, but once our user starts adding in product we ALWAYS want to remind him what he has!

Then let's switch ...

switch ($step) {
case 1:
        // Offer Selection of goods from aisles
        // Work out the products on the current Aisle
                $ci = "<table border=1>";
                reset($products);
                while (list($code,$product) = each ($products)) {
                        if ($product[aisle] == $currentaisle) {
                                $ci .= "<tr>";
                                $ci .= "<td>".$product[title]."</td>";
                                $ci .= "<td>".$product[days]."</td>";
                                $ci .= "<td><input size=3 name=__".$code.
                                                " value=".$cart[$code].
                                                "></td>";
                                $ci .= "</tr>";
                        }
                }
                $ci .= "</table>";
                $fill["ci"] = $ci."<input type=submit value='update cart'><hr>";


Going through the array of available products, we making up a table of all those that are on the current aisle. Each product has a part code, which we're pre-pending with __ to give a field name that we'll be easily able to identify as a product code field.

We're now running code that will be common with all the pages that we generate while our user's browsing our shelves. Notice how we pre-fill the user entry box with the number of products he has already selected of each type, allowing him to increase or decrease the number of each product easily.

// Work out other departments

                $od = "You are currently in ".$aisles[$currentaisle]."<br>";
                reset ($aisles);
                while ($aisle = each($aisles)) {
                        if ($aisle[key] == $currentaisle) continue;
                        $od .= 'Go to <input type=submit name=jump value="'.
                                $aisle[value].'"><br>';
                }
                $fill["od"] = $od;

That code works out a table of all the other aisles that are available, and writes a series of submit buttons to allow the user to single-click navigate the store. Finally, the "step 1, phase c" code produces a summary line to tell us a total of what's in the basket. As this is the initial entry to a script, the answer will be nothing!

        // Summarise what's in the basket so far

                $haveitems or $fill[bask] .= "Nothing in the cart yet<br>";
                if ($haveitems) {
                        $fill["bask"] .= "TOTAL TRAINING IN BASKET - $totaldays days<br><br>";
                        $fill["bask"] .= "Select <input type=submit name=go_out value=here> to".
                        " checkout the contents of your basket<br>";
                        }
                $nextpage = "offer.htp";
                break;

You'll have notices the setting of $nextpage; that's the variable that contains the template for shopping around the store. The very end of our carter.php4 script calls a function to read and complete that template, and then spits it out to the browser.

$html = filltemplate($nextpage,$fill);
print ($html);
?>

UNDER THE HOOD

The storefront generation probably looked shorter that you expected - and that's largely because much of the work is done elsewhere.

getshop.inc includes the readproducts function that reads in our data file and populated various variables, and the filltemplate function.

There's nothing very special about readproducts, although if you're not familiar with arrays of arrays you might find it a bit daunting. Other little things to note in the code:
 - Use of &$ in the parameters to call by name
 - Provision of a capability to comment the data file
 - Use of explode on a tab separated variable file
   (very useful if your data is likely to come from Excel)
 - Telling our data line types apart by counting the fields.
<?php

function readproducts(&$products,&$aisles,&$currentaisle) {

// Data file products has lines as follows:
// Store title line # no tabs
// P Perl Courses # one tab
// P PP 4 Perl Programming # three tabs

// Read in the products that we offer!

$fh = fopen("products","r");
while (! feof($fh)) {
        $parts = explode("\t",preg_replace("/#.*/","",fgets($fh,1024)));
        if (count($parts) < 2) {
                # Title record or nothing
                if (ereg("[[:graph:]]",$parts[0])) {
                        $title = $parts[0];
                        }
                continue;
                }
        if (count($parts) < 4) {
                # Aisle Record
                        $aisles[$parts[0]] = $parts[1];
                        $defaultaisle or $defaultaisle = $parts[0];
                        if (trim($_POST[jump]) == trim($parts[1]))
                                $currentaisle = $parts[0];
                } else {
                # Product Record
                        $products[$parts[1]][aisle] = $parts[0];
                        $products[$parts[1]][days] = $parts[2];
                        $products[$parts[1]][title] = $parts[3];
                }
        }

$currentaisle or $currentaisle = $defaultaisle;

return $title;
}

function filltemplate($file, $filler) {
        $fh = fopen($file,"r");
        $html = fread($fh,filesize($file));
        foreach (array_keys($filler) as $fld) {
                $html = preg_replace("/%$fld%/",$filler[$fld],$html);
        }
        return $html;
}

?>

The products data file that we used for the illustrations in these notes is

Scheduled Course Shop
P Perl Courses
H PHP Courses
M MySQL training Courses
J Java Courses
T Tcl Courses
P PP 4 Perl Programming
P LP 5 Learning to program in Perl
P PL 3 Perl for larger projects
P PW 2 Perl for the Web
H PG 1 Technology for PHP
H PH 3 PHP Programming
M MQ 2 MySQL
J JP 5 Java Programming for the Web
T TB 3 Tcl and Expect basics
T TK 2 Tk Programming

We won't explain the data file format here, since we've correctly commented it in the code above!

The other function in getshop.inc is filltemplate; just a few lines of code, but a powerful loop and regular expression performs a mailmerge type operation, removing special tags from the HTML and replacing them with parameter values. Any similarity to the structure of PHP itself is, of course, totally accidental.

Here's the template before it was filled in:

<html>
<head><title>%storename% - Product selection</title></head>
<body bgcolor=white>
<center><h1>%storename%, step %step%</h1></center>
Please feel free to browse around our store and
make your selections. You may update how many of each
product in the current aisle you want, and place them in
your cart using either the update button, or a button to
select a different aisle. Once you have completed your
selections from all the aisles, select the checkout button
and you'll be asked for your details. Finally, you'll be
asked for your credit card information on our secure server.
<form method=post>
<table border=1><tr><td>
<h2>Current Aisle selection</h2>
%ci%
<h2>Move to a new aisle</h2>
%od%
</td><td>
%bask%
</td></tr>
</table>
</form>
</html>

The layout of pages such as this can be developed through an application such as Dreamweaver - laying out of tables within tables, conforming exactly to the w3 standards for coding, getting and javascript exactly right, etc., is much better done with tools than with a text editor!

ADDING TO THE SHOPPING CART

Once the front page has been generated most of the hard work has really been done; to add products to the shopping cart, the only extra code is step 1, phase a:

        switch ($step) {
        // Incorporate selection of goods from aisles
        case 1:
                reset($_POST);
                while ($item = each($_POST)) {
                        if (ereg("^__(.*)",$item[key],$got)) {
                                $cart[$got[1]] = $item[value];
                                }
                        }
                if ($_POST[go_out]) $step = 2; // Move on to checkout!
                break;

which takes any field values starting __ and adds them to the cart. It also notes a move on to phase 2 (user's information entry) if the submit button inviting a checkout has been selected.


USER DETAILS

User details - step 2 - brings some new issues. We require the user to enter a number of fields each of which is to be checked as best as we can to make sure that we have input and it's sensible ... we then want to go back to the step 2 page flagging that we can't carry on until it's been filled in properly ... OR we want to move on if we can.

and we'll validate any data entered with the following code:

        // Entry of user's name and address
        case 2:
                if (ereg("[[:graph:]]@[[:graph:]]",$_POST[email])) {
                        $details[email] = $_POST[email];
                } else {
                        $alert[xemail] = "<BR>REQUIRED";
                        }

                if (ereg("[[:graph:]]",$_POST[name])) {
                        $details[name] = $_POST[name];
                } else {
                        $alert[xname] = "<BR>REQUIRED";
                        }

                if (ereg("[[:graph:]]",$_POST[postal])) {
                        $details[postal] = $_POST[postal];
                } else {
                        $alert[xpostal] = "<BR>REQUIRED";
                        }

                if ($details[name] and $details[email] and $details[postal]) $step=3;
                break;

Good use of regular expressions - and notice that once we get a valid input we copy it into the session variables - we don't want to loose it!

If an entry is incorrect or missing, we set up an extra variable with a little error message - when we look at the template for this page in a moment, you'll see that we have provided a whole series of "fillin"s so that we can get back to the user. Just concluding the code above, though - if we have valid inputs for all of the fields we need, we can move on to step 3!

Here's the variable part of the the user information form:

<h2>Please tell us about yourself</h2>
<table border=1>
<tr><td>Name%xname%</td><td><input name=name value="%name%"></td></tr>
<tr><td>Email%xemail%</td><td><input name=email value="%email%"></td></tr>
<tr><td>Postal%xpostal%</td><td><textarea name=postal cols=25 rows=4>%postal%</textarea></td></tr>
</table>

We'll get an error diplay if we make an error, then go back to step 2. If we get it right, we'll be moved on to step 3.

That's better ;-)

THE FINAL PHASE

Once the credit card details have been accepted (we hope you switch to a secure server!), our script needs to email the user to confirm payment, place the order on a database, and destroy the session. Here's our sample code:

case 4:
          // Order placed. Send email. Write to database.
          session_destroy();
          $nextpage = "thankyou.htp";
          $fill["bask"] .= "TOTAL TRAINING IN BASKET - $totaldays days<br><br>";
          break;

You'll note that $step has increased up to 4, even though we only declare three phases to the user. Ah - and there was a step 0 as well, if you recall - so it's really a five step process.

Although there might be substantial work to do at this last stage, the final display from carter.php4 is a simple screen thanking our user for his order, telling him when and how to accept delivery, how to contact us for any reason should he need to do so, and wishing him well with our product.

Take care to ensure that you don't leave your user stranded when he's finished buying ... offer him an onward going link. Since our site in this example was offering training courses, shall we send our customer to the joining instructions??

COMPLETE CARTER.PHP4 FILE

Here's a complete listing of the carter.php4 file that you've seen in sections throughout this module. You'll find it ties together quite easily once you're happy with the batchlet concept. Programming is an art as well as a science, and in the case of a PHP application like this, the artist needs to come to the fore.


n which aisle of a store. Users may browse around the aisles, and later
may check out via a secure server */

session_start();
include_once("getshop.inc");
$trace = ""; # Logging variable
$alert = array(xpostal => "", xemail => "", xname => "", xcard => "");

//////////////////// THINGS TO BE DONE ON ENTRY TO A PAGE

if (! session_is_registered("cart")) {
        // New session - initialise for order entry
        session_register("cart","currentaisle","step","details");
        $cart = array(); # Current cart contents
        $details = array(email => "", postal =>"",
                name => "", card => ""); # Details of person
        $step = 1; # How far through?
                                        # 1 - selecting from aisles
                                        # 2 - giving own details
        $currentaisle = "";
} else {
        switch ($step) {
        // Incorporate selection of goods from aisles
        case 1:
                reset($_POST);
                while ($item = each($_POST)) {
                        if (ereg("^__(.*)",$item[key],$got)) {
                                $cart[$got[1]] = $item[value];
                                }
                        }
                if ($_POST[go_out]) $step = 2; // Move on to checkout!
                break;
        // Entry of user's name and address
        case 2:
                if (ereg("[[:graph:]]@[[:graph:]]",$_POST[email])) {
                        $details[email] = $_POST[email];
                } else {
                        $alert[xemail] = "<BR>REQUIRED";
                        }

                if (ereg("[[:graph:]]",$_POST[name])) {
                        $details[name] = $_POST[name];
                } else {
                        $alert[xname] = "<BR>REQUIRED";
                        }

                if (ereg("[[:graph:]]",$_POST[postal])) {
                        $details[postal] = $_POST[postal];
                } else {
                        $alert[xpostal] = "<BR>REQUIRED";
                        }

                if ($details[name] and $details[email] and $details[postal]) $step=3;
                break;
        case 3:
                if (ereg("[[:digit:]]{16}",$_POST[card])) {
                        $details[card] = $_POST[card];
                } else {
                        $alert[xcard] = "<BR>Just 16 digits<BR>REQUIRED";
                        }
                if ($details[card] ) $step=4;
                break;

        }
}


//////////////////// ANY GENERAL WORK TO BE DONE

$fill = array_merge($details,$alert);
$fill[storename] = readproducts($products,$aisles,$currentaisle);
$fill[step] = $step;
$fill[totsteps] = 3;

//////////////////// THINGS TO PREPARE FOR NEXT PAGE

// Always need to evaluate what's in the cart!

$bask = "";
reset ($cart) ;
while ($item = each($cart)) {
        if ($item[value] > 0) {
                $haveitems = 1;
                $blines .= "<tr><td>".$products[$item[key]][title].
                        "</td><td>".
                        $item[value].
                        "</td><td>".
                        $products[$item[key]][days].
                        "</td><td>".
                        ($dc = $item[value]*$products[$item[key]][days]).
                        "</td></tr>";
                $totaldays += $dc;
        }
}
if ($blines) $bask = "<table border=1><tr><th>Product</th><th>places</th>".
                        "<th>duration</th><th>total days</th></tr>$blines</table><br><br>";
$fill["bask"] = $bask;

////// Following depend on what next page is to be!

switch ($step) {
case 1:
        // Offer Selection of goods from aisles
        // Work out the products on the current Aisle
                $ci = "<table border=1>";
                reset($products);
                while (list($code,$product) = each ($products)) {
                        if ($product[aisle] == $currentaisle) {
                                $ci .= "<tr>";
                                $ci .= "<td>".$product[title]."</td>";
                                $ci .= "<td>".$product[days]."</td>";
                                $ci .= "<td><input size=3 name=__".$code.
                                                " value=".$cart[$code].
                                                "></td>";
                                $ci .= "</tr>";
                        }
                }
                $ci .= "</table>";
                $fill["ci"] = $ci."<input type=submit value='update cart'><hr>";

        // Work out other departments

                $od = "You are currently in ".$aisles[$currentaisle]."<br>";
                reset ($aisles);
                while ($aisle = each($aisles)) {
                        if ($aisle[key] == $currentaisle) continue;
                        $od .= 'Go to <input type=submit name=jump value="'.
                                $aisle[value].'"><br>';
                }
                $fill["od"] = $od;

        // Summarise what's in the basket so far

                $haveitems or $fill[bask] .= "Nothing in the cart yet<br>";
                if ($haveitems) {
                        $fill["bask"] .= "TOTAL TRAINING IN BASKET - $totaldays days<br><br>";
                        $fill["bask"] .= "Select <input type=submit name=go_out value=here> to".
                        " checkout the contents of your basket<br>";
                        }
                $nextpage = "offer.htp";
                break;
case 2:
                $nextpage = "getaddy.htp";
                $fill["bask"] .= "TOTAL TRAINING IN BASKET - $totaldays days<br><br>";
                break;
case 3:
                $nextpage = "getccard.htp";
                $fill["bask"] .= "TOTAL TRAINING IN BASKET - $totaldays days<br><br>";
                break;
case 4:
                // Order placed. Send email. Write to database.
                session_destroy();
                $nextpage = "thankyou.htp";
                $fill["bask"] .= "TOTAL TRAINING IN BASKET - $totaldays days<br><br>";
                break;
        }

$html = filltemplate($nextpage,$fill);
print ($html);
?>


See also Training module covering this subject and example source code

Please note that articles in this section of our web site were current and correct to the best of our ability when published, but by the nature of our business may go out of date quite quickly. The quoting of a price, contract term or any other information in this area of our website is NOT an offer to supply now on those terms - please check back via our main web site

Related Material

Shopping Cart Application in PHP
  [3820] PHP sessions - a best practice teaching example - (2012-07-27)
  [1634] Kiss and Book - (2008-05-07)
  [1623] PHP Techniques - a workshop - (2008-04-26)
  [1487] Efficient PHP applications - framework and example - (2007-12-28)
  [1321] Resetting session based tests in PHP - (2007-08-26)

PHP - Further Web Page and Network Handling
  [4483] Moving from mysql to mysqli - simple worked example - (2015-05-03)
  [4070] Passing variable between PHP pages - hidden fields, cookies and sessions - (2013-04-26)
  [3918] Multiple page web applications - maintaining state - PHP - (2012-11-10)
  [3568] Telling which ServerAlias your visitor used - useful during merging domains - (2012-01-04)
  [3540] Easy session example in PHP - keeping each customers data apart - (2011-12-06)
  [3432] 3 digit HTTP status codes - what are they, which are most common, which should be a concern? - (2011-09-11)
  [3036] Sending out an email containing HTML from within a PHP page - (2010-11-07)
  [2918] Downloading a report from the web for further local analysis - (2010-08-13)
  [2729] Uploading a document or image to its own URL via a browser - (2010-04-18)
  [2679] How to build a test harness into your PHP - (2010-03-16)
  [2632] Shipping a test harness with your class in PHP - (2010-02-12)
  [1549] http, https and ajp - comparison and choice - (2008-02-22)
  [1518] Downloading data for use in Excel (from PHP / MySQL) - (2008-01-25)
  [1515] Keeping staff up to date on hotel room status - (2008-01-22)
  [1505] Script to present commonly used images - PHP - (2008-01-13)
  [1496] PHP / Web 2 logging - (2008-01-06)
  [1495] Single login and single threaded models - Java and PHP - (2008-01-04)
  [1485] Copyright and theft of images, bandwidth and members. - (2007-12-26)
  [1379] Simple page password protection - PHP - (2007-10-04)
  [1355] .php or .html extension? Morally Static Pages - (2007-09-17)
  [1210] PHP header() function - uses and new restrictions - (2007-05-30)
  [1187] Updating a page strictly every minute (PHP, Perl) - (2007-05-14)
  [1183] Improving searches - from OR to AND? - (2007-05-11)
  [1114] PHP Image upload script - (2007-03-21)
  [1009] Passing GET parameters through Apache mod_rewrite - (2006-12-27)
  [936] Global, Superglobal, Session variables - scope and persistance in PHP - (2006-11-21)
  [904] Of course I'll tell you by email - (2006-10-25)
  [847] Image maps for navigation - a straightforward example - (2006-08-28)
  [789] Hot answers in PHP - (2006-07-02)
  [767] Finding the language preference of a web site visitor - (2006-06-18)
  [675] Adding PHP tags to an old cgi program - (2006-04-08)
  [603] PHP - setting sort order with an associative array - (2006-02-13)
  [565] Using PHP to output images, XML, Style sheets, etc - (2006-01-15)
  [542] Morning image, afternoon image - (2005-12-26)
  [537] Daily Image Santafied - (2005-12-22)
  [484] Setting the file name for a downloaded document - (2005-11-03)
  [451] Accessing a page via POST from within a PHP script - (2005-09-26)
  [443] Server side scripting of styles to suit the browser - (2005-09-12)
  [425] Caching an XML feed - (2005-08-26)
  [410] Reading a news or blog feed (RSS) in your PHP page - (2005-08-12)
  [376] What brings people to my web site? - (2005-07-13)
  [372] Time calculation in PHP - (2005-07-08)
  [356] Sudoku helper or sudoku cheat - (2005-06-23)
  [345] Spotting a denial of service attack - (2005-06-12)
  [314] What language is this written in? - (2005-05-17)
  [220] When to use Frames - (2005-02-19)

resource index - PHP
Solutions centre home page

You'll find shorter technical items at The Horse's Mouth and delegate's questions answered at the Opentalk forum.

At Well House Consultants, we provide training courses on subjects such as Ruby, Lua, Perl, Python, Linux, C, C++, Tcl/Tk, Tomcat, PHP and MySQL. We're asked (and answer) many questions, and answers to those which are of general interest are published in this area of our site.

You can Add a comment or ranking to this page

© WELL HOUSE CONSULTANTS LTD., 2019: Well House Manor • 48 Spa Road • Melksham, Wiltshire • United Kingdom • SN12 7NY
PH: 01225 708225 • FAX: 01225 793803 • EMAIL: info@wellho.net • WEB: http://www.wellho.net • SKYPE: wellho

PAGE: http://www.wellho.net/solutions/php-shop ... n-php.html • PAGE BUILT: Wed Mar 28 07:47:11 2012 • BUILD SYSTEM: wizard