Core Web Application Development with PHP and MySQL

Being our most complex sample yet, and having more serious security implications than any sample thus far (after you take users' credit card numbers, you are playing a much more serious game), we want to be sure that we have thought this application through extremely well before we start writing code for it. Figure 33-3 shows the complete set of possible page progressions for this web application.

Figure 33-3. Using the poster store ecommerce sample.

Core Structure of the Sample

The poster store sample is made up of a bit more code than the other applications we have written thus far. Because we have to build up a list of products to sell, it also has a bit more setup than the rest of the samples. The posterstore.mysql script that you run to set up the database tables executes a number of other scripts to populate various tables in the database.

Products

The key to our product listings is the Products table in the database. In this table, we store all the information about any poster we have for sale, including its name or title, the size of the print, the cost per print, and a description for it. Finally, the name of a thumbnail image is stored for the print so that we can display this in the product listing pages. The Products table and the items in it are managed by the ProductManager class in the sample.

The Shopping Cart

The shopping cart that users fill as they browse through the store is an array of CartItem objects, which are managed by the CartManager class. As users add a new item to the cart, we check to see whether there is an item of that type in the cart already, in which case we just increment the number of items the user wants to purchase. Otherwise, we add a new item to the cart with the appropriate details.

The key decision to make with regard to the shopping cart is where to store it. There are two obvious choices:

  1. In the database

  2. In the session data

Storing the cart in the database gives us a way of making a shopping cart last between visits for users (we can send them a cookie to tell us which cart they "own") and lets us periodically run analyses to see what users are placing in their carts and actually purchasing or removing. However, the logic required to manage and periodically clean up such a system is non-trivial and might not be worth it for smaller online vendors.

Storing the cart in the session data provides for an extremely easy way of associating the cart with the currently browsing user. It is automatically cleaned up for us by PHP's session management code. Accessing the cart is fast and is programmatically convenient. To obtain the contents of cart, we just have to type this:

$_SESSION['cart']

The major downside to storing the shopping cart in the session data is that it is lost every time users close their browser(s) and leaves us no record of what users keep in their carts at any given time.

Checkout

After users have viewed the contents of their cart and decided they want to proceed with the purchase, they begin the checkout process (see Figure 33-4). For this, we collect three primary clusters of information from them:

  • Their personal information, including billing address, e-mail address, and password

  • The name and address to which the posters will be shipped

  • The means via which they will pay for the products (credit card information)

Figure 33-4. Checking out after you have finished shopping.

After all of this information has been collected, we provide an order review page, where users can see all the information for the entire order and make any last-minute changes if desired. User and billing information is stored in the Users table in the database. Users coming back to the site have the option of using the same e-mail address and password that they entered the last time they visited.

Entering the Order

Orders are stored in our databases in two phases. We first create an entry in the Orders table, with the shipping information and cost of the various parts of the order (products and shipping). We then create an entry in the OrderEntries table for each type of item in the cart. These entries are associated with the order via the order_id field and contain the product ID of the item, the price we sold it for, and the quantity.

The final step of storing order information comes after we have confirmed payment. After this is done, we put the confirmation number for this payment in the record in the Orders table with the appropriate order ID. The order is now "complete" and moves to the process of actually filling it. For this sample, we leave this detail omitted. A real application could work with an external device such as a printer, send an e-mail, or even just write an entry into a log file for later processing by other scripts. To help facilitate this, we created the order_status field in the Orders table.

Page Layout

The poster store ecommerce application operates differently from the samples we have seen thus far in that users work with different pages at different times. Initially, users will mostly be working with a page to show them products (reached from the home page) and a page to show them the contents of their shopping cart.

After users have completed their shopping and want to check out, they proceed to the sequence of pages for checking out. These pages are, in order, the user information, shipping information, and payment information pages. These finish at the order review page, where the user can make changes or submit the order for processing. After a successful order, the user is sent to a confirmation page. Figure 33-5 shows a complete diagram of these pages.

Figure 33-5. Page progressions in our poster store Ecommerce application.

The key pages in this web application are as follows:

index.php The home page for the application and the point from which users begin browsing through our inventory.

showprods.php This page shows the products in our store and lets users select what size of posters to display. Users can add items to their shopping cart from this page.

showcart.php Users can view the contents of their cart from this page and choose to either continue shopping or begin the checkout process.

checkout.php This is the first page in the checkout process. It gives the user the chance to either log in as an existing user or begin the process as a new user.

userinfo.php The first stage of checkout, where users enter their personal and billing information (see Figure 33-6).

Figure 33-6. Entering your user and billing information.

shipinfo.php The second stage of checkout, where users enter the shipping information for the order.

pmtinfo.php The third stage of checkout, where users enter the payment information for the order, including credit card details.

orderreview.php This is the last stage in checkout. It gives users a chance to review their order before submitting it.

In addition to these pages, there is the usual page for displaying errors in our application (error.php) and a confirmation page (success.php) when the order is complete. We also provide a help.php page, which we include more as a placeholder than anything else. We should always try to provide help for our users, so we give them a description of our offerings and how to use the site.

Database Structure

Although we have mentioned the core tables with which this database works, we have not discussed their exact structure. We will continue to use indexes and foreign keys in our tables which will require the use, in MySQL at least, of an appropriate table storage engine. (See the section titled, "Table Storage Engines" in Chapter 9, "Designing and Creating Your Database.") In addition, we will use transactions in this chapter. We therefore declare many of our tables to use the InnoDB storage engine.

We create our database and the user accounts that can access it as follows:

CREATE DATABASE PosterStore DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; GRANT CREATE, DROP, SELECT, UPDATE, INSERT, DELETE ON PosterStore.* TO 'posters_admin'@'localhost' IDENTIFIED BY 'posters_admin'; GRANT SELECT, UPDATE, INSERT, DELETE ON PosterStore.* TO 'posters_user'@'localhost' IDENTIFIED BY 'posters_user'; USE PosterStore; SET NAMES 'utf8';

Our Products table, which does not require transaction support, is created as follows:

CREATE TABLE Products ( product_id INTEGER AUTO_INCREMENT PRIMARY KEY, title VARCHAR(150) NOT NULL, description TEXT, size INTEGER NOT NULL, price DECIMAL(6,2) NOT NULL, thumbnail VARCHAR(50), FOREIGN KEY (size) REFERENCES Sizes (size_id) ) ENGINE = InnoDB;

Instead of trying to keep track in numerous places in code of the possible sizes in which posters can be purchased, we just create a new table in the database with allowable size values, to which the size field in the Products table refers. We also fill it with some core values:

CREATE TABLE Sizes ( size_id INTEGER PRIMARY KEY, size_desc VARCHAR(20) NOT NULL ) ENGINE = InnoDB; LOAD DATA LOCAL INFILE 'posterstore.data.sizes.mysql' INTO TABLE Sizes FIELDS ENCLOSED BY '\'' LINES TERMINATED BY '\r\n' (size_id,size_desc);

Next come the tables for the orders themselves, which number three: the OrderEntries table for order entries; the Orders table for the orders themselves; and an OrderStatus table, with possible values for the order_status field in the Orders table. In addition to information on the cost of the order, the Orders table holds the shipping information:

CREATE TABLE OrderEntries ( oentry_id INTEGER AUTO_INCREMENT PRIMARY KEY, order_id INTEGER NOT NULL, product_id INTEGER NOT NULL, price DECIMAL(6,2) NOT NULL, num_units INTEGER NOT NULL, FOREIGN KEY (order_id) REFERENCES Orders (order_id), FOREIGN KEY (product_id) REFERENCES Products(product_id) ) ENGINE = InnoDB; CREATE TABLE Orders ( order_id INTEGER AUTO_INCREMENT PRIMARY KEY, customer_id INTEGER NOT NULL, ship_name VARCHAR(150) NOT NULL, ship_company VARCHAR(100), ship_address1 VARCHAR(150) NOT NULL, ship_address2 VARCHAR(150), ship_city VARCHAR(100) NOT NULL, ship_state VARCHAR(75) NOT NULL, ship_postal VARCHAR(25) NOT NULL, ship_country VARCHAR(100) NOT NULL, ship_phone VARCHAR(25) NOT NULL, order_cost DECIMAL(6,2) NOT NULL, ship_cost DECIMAL(6,2) NOT NULL, total_cost DECIMAL(6,2) NOT NULL, pmt_type INTEGER NOT NULL, billing_conf VARCHAR(100), order_status INTEGER NOT NULL, order_filled_by VARCHAR(100), FOREIGN KEY (customer_id) REFERENCES Users (user_id), FOREIGN KEY (order_status) REFERENCES OrderStatus (status_id) ) ENGINE = InnoDB; CREATE TABLE OrderStatus ( status_id INTEGER AUTO_INCREMENT PRIMARY KEY, status VARCHAR(30) NOT NULL ) ENGINE = InnoDB;

Note that the OrderEntries and Orders tables, both requiring transaction support, are declared to use the InnoDB engine.

Our last major table, the Users table, holds the customer's personal and login information. This table is used if the user wants at a later time to log back in using an e-mail address and password. We declare this table as follows in MySQL:

CREATE TABLE Users ( user_id INTEGER AUTO_INCREMENT PRIMARY KEY, email VARCHAR(200) NOT NULL, password VARCHAR(200) NOT NULL, name VARCHAR(150) NOT NULL, company VARCHAR(100), address1 VARCHAR(150) NOT NULL, address2 VARCHAR(150), city VARCHAR(100) NOT NULL, state VARCHAR(75) NOT NULL, postal VARCHAR(25) NOT NULL, country VARCHAR(100) NOT NULL, phone VARCHAR(25), INDEX(email) ) ENGINE = InnoDB;

We create an index on the email field because we will perform searches on that column when the user tries to log back in to our system.

Our final table holds the names of the various credit card types we support in our application. This table, CreditCards, would probably not be used in a real-world ecommerce application, because it is likely that this information would come from whichever payment processing facility we decided to use. For the purposes of our sample, we create this table and populate it with some reasonable values:

CREATE TABLE CardTypes ( card_id INTEGER PRIMARY KEY, card_name VARCHAR(50) ); LOAD DATA LOCAL INFILE 'posterstore.data.cardtypes.mysql' INTO TABLE CardTypes FIELDS ENCLOSED BY '\'' LINES TERMINATED BY '\r\n' (card_id,card_name);

UI Strategy

For the user interface in this web application, we will go back to the strategy of using file inclusion containing much of the XHTML markup for our pages. We will write a pagetop.inc and pagebottom.inc, which we will include in our visual pages. One twist we will add is that we will only show the top menu or navigation bar on those pages that are not part of the checkout process. For this latter group, we want to avoid giving users a reason to interrupt the sequence of pages and jump to somewhere else. Those scripts that set the $g_dontShowNavBar variable will not see this.

Our pagetop.inc looks like this:

<!DOCTYPE html PUBLIC "~//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang= "en-US"> <head> <title><?php echo $page_title; ?></title> <meta http-equiv="content-type" content="text/html; charset= utf-8"/> <link rel="stylesheet" href="basestyle.css" type="text/css"/> </head> <body> <table width='100%' border='0' cellspacing='0' cellpadding='0'> <tr> <td align='right'> <?php if (isset($g_dontShowNavBar)) { ?> <font class='topMenuBarFont'> <a class='topMenuLink' href='showcart.php'>View Cart</a> | <a class='topMenuLink' href='orderstatus.php'>Order Status</a> | <a class='topMenuLink' href='help.php'>Help</a> | <a class='topMenuLink' href='showprods.php'>Browse Products</a> </font> <?php } else { ?> &nbsp; <?php } ?> </td> </tr> </table> <table width='100%' border='0' cellspacing='0' cellpadding='0' class='topMenuBar'> <tr> <td><hr size='1'/> </tr> <tr> </td> <td> &nbsp; <a class='bigTitle' href='index.php'> the world in posters </a> </td> </tr> <tr> <td><hr size='1'/> </tr> </table>

As before, our pagebottom.inc script is trivial:

<p align='center'> <font class='smallPrint'> Copyright &copy; 2005 Teeny-Tiny Stores Inc. All Rights Reserved.<br/> <a class='smallLink' href='privacy.php'>Privacy Policy</a> | <a class='smallLink' href='aboutus.php'>About Us</a> </font> </p> </body> </html>

Complete File Listing

Being a slightly larger sample, we will list our files by directory. The first group is the script files in the webapp/ subdirectory of the sample, shown in Table 33-1. Table 33-2 shows the include and support library files in the libs/ directory.

Table 33-1. A Complete Listing of Files in the webapp/ Directory of the Poster Store Ecommerce Sample

Filename

Type

Description

addtocart.php

Processing script

The user has clicked on the Add to Cart button next to an item in the product display page. This script adds the item to the user's shopping cart.

basestyle.css

CSS

The style sheet associated with this application.

checkout.php

User page

Begins the process of checking outlets users continue as a new user or log in as an existing user.

error.php

User page

Used to show complex errors to the user (not simple input errors).

existinguser.php

Processing script

If the user has entered a username and password in the checkout page, this file processes that information and validates it.

index.php

User page

The home page for our application, from which the user can begin browsing the posters we have for sale.

orderreview.php

User page

The last page of the checkout process, this page gives users a last chance to review or change their order.

pmtinfo.php

User page

The third stage of the checkout process, this page lets users enter the payment information for their order.

shipinfo.php

User page

The second stage of the checkout process, this page is where users enter the shipping information for their order.

showcart.php

User page

This page lets users view the current contents of their cart. From here, they can either check out or continue shopping.

showprods.php

User page

Users can browse and view the products we have for sale from this page.

submitorder.php

Processing script

After the user has confirmed everything in the order review page and clicked the Submit button, this script processes the order and either sends the user back if there was a problem with payment information or sends him to the success page.

submitpmtinfo.php

Processing script

Processes and validates the payment information the user entered in the checkout process.

submitshipinfo.php

Processing script

Processes and validates the shipping information the user entered in the checkout process.

submituserinfo.php

Processing script

Processes and validates the personal and billing information the user entered in the checkout process.

success.php

User page

After the order has been completed, this page is shown to the user on success.

updatequantity.php

Processing script

If users want to change the quantity of an item in their shopping cart, they visit this page.

userinfo.php

User page

This is the first page in the checkout sequence. It is where users enter personal and billing information.

Table 33-2. A Complete Listing of Files in the libs/ directory of the Poster Store Ecommerce Sample

Filename

Type

Description

cartmanager.inc

Library

Contains the CartManager class, which manages the items in a user's shopping cart.

coreincs.inc

Organizational helper

Included by all of the pages in the webapp/ directory to ensure that they have the correct libraries included to operate properly.

creditcard.inc

Library

Contains a couple of classes to help us simulate working with credit cards in our application. The exact way in which this is done depends on what payment processing facility you use in a real application.

dbconninfo.inc

Database helper

Contains the information needed to connect to the database.

dbmanager.inc

Library

Contains a class used to connect to the database.

errors.inc

Organizational helper

Contains a set of functions and exception classes to help with error handling.

ordermanager.inc

Library

Contains the OrderManager object. It is the way in which we manage orders with the middle tier.

pagebottom.inc

User interface

Contains the bottom portion of the shared user interface in our pages.

pagetop.inc

User interface

Contains the top portion of the shared user interface for our visual pages.

paymentmanager.inc

Library

Contains the code that we will use to simulate actually processing payments in our system.

productmanager.inc

Library

Holds the ProductManager class, which we use to list and obtain products available in our store.

session.inc

Organizational helper

Sets up all the code that we need for our session including some security features.

usermanager.inc

Library

Contains the UserManager class, via which we store new and manage existing customers of our store.

validate.inc

Library

Contains a number of routines that we use to validate the input a user sends us from the various forms. This lets us share as much of the validation code as possible.

Категории