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:
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:
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 { ?> <?php } ?> </td> </tr> </table> <table width='100%' border='0' cellspacing='0' cellpadding='0' class='topMenuBar'> <tr> <td><hr size='1'/> </tr> <tr> </td> <td> <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 © 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.
|
Категории