The Auction Application

Let's begin by looking at the code we use to generate the index page-since this is where the auction site begins. Figure 7-1 is the index page. Since we are using templates for most of the HTML, changing the look and feel of this page is very easy. This page is mostly informational; it tells about the auction and has a link that allows you to view all of the items. There is also a drop-down list that has the items listed. Obviously, if you have a lot of auction items, this drop-down list gets very large and unruly. Only use it if your list of items is of a reasonable size. You can decide what reasonable means to you.

click to expand
Figure 7-1: index.cgi page

Now let's take a look at the code for the index page.

index.cgi

start example
01: #!/usr/bin/perl -wT 02: # index.cgi 03: # Auction program main page.
end example

Line 1 tells the system where to find Perl and turns on warnings and taint checking. Warnings provide more informational messages when the program runs. Taint checking is used to keep the program from accidentally using unchecked data to perform potentially dangerous operations.

Lines 2-3 are simply comments about the program.

04: use strict; 05: use CGI qw(:standard); 06: use DBI; 07: use lib qw(.); 08: use SmallAuction;

Line 4 turns on strict. Always use strict in your programs. It is 100-times easier to begin all programs with strict than it is to try and go back and put strict into programs that are already written. strict forces you to follow stringent programming guidelines that are designed to help keep you from making silly programming mistakes.

For instance, if you have strict on and you mistype a variable name, it will generate an error. If you do not have strict on and you make the same mistake, Perl will automatically create the new variable and will continue happily along, using the wrong variable now. This can be very hard to troubleshoot.

Line 5 loads the CGI module and the :standard functions. This module is used to gather HTML form data and to do other CGI-related tasks.

Line 6 loads the DBI module. This is the module we use for database connectivity.

Line 7 uses the lib function to cause Perl to add the path provided to the @INC array. The @INC array is what Perl uses to search for modules. In this case, we add the dot (.) to the path, which means adding the current directory.

Line 8 loads the SmallAuction module. This module is located in the current directory-which is why we have to add it to the @INC array in the preceding line.

09: $ENV{PATH} = ‘'; 10: my $tmpl   = "templates"; 

Line 9 sets the PATH environment variable to an empty string. Many times, when you have taint checking on, you will get an error message like this:

Insecure $ENV{PATH} while running -T switch at /usr/local/lib/foo line 432.

Simply setting the $ENV{PATH} variable in the program gets rid of this problem. If you need certain paths in the $ENV{PATH} variable, put them in place of the empty string. For example, $ENV{PATH}="/bin:/usr/bin"

Line 10 creates a scalar variable named $tmpl and sets it to the string "templates". This is the directory where the template files are located.

11: my $a_id = 1;  # Auction id number.

Line 11 creates a variable named $a_id and sets it to 1. This is the auction ID we use to get the proper auction information from the database.

12: my $dbh = DBI->connect("dbi:mysql:auction", "bookuser",         "testpass")  13:     or die("Error! $!\nAborting");

Lines 12-13 connect us to the database. The connect method of the DBI module is what we use to create the connection. We store a handle to the connection in the variable $dbh that we declare on line 12. If the connection fails, line 13 will display an error message telling us about it.

14: my %item = (); 15: print header;

Line 14 declares a hash variable named %item and initializes it to an empty set.

Line 15 prints the results of a call to the header function. The header function is part of the CGI module and is used to print a valid HTTP header.

16: $item{item_list}    = Drop_Down_Item_List($dbh, $a_id,          "name"); 17: $item{auction_name} = Page_Header($dbh, $tmpl, $a_id);

Line 16 calls the Drop_Down_Item_List function and stores the results in the %item hash under the item_list key. This function is in the SmallAuction module and is used to get all of the auction items and return them in a format to display them in an HTML "select" list. The items passed are the database handle ($dbh), the auction ID ($a_id), and the field we want to sort the data on (name).

Line 17 calls the Page_Header function used to display the HTML for the beginning of the page. This function is also part of the SmallAuction module and is passed the database handle ($dbh), the template directory ($tmpl), and the auction ID ($a_id).

18: Print_Page($tmpl, "index.tmpl", \%item); 19: $dbh->disconnect;

Line 18 calls the Print_Page subroutine. This subroutine is part of the SmallAuction module. This function is used to print the HTML pages generated by the templates. This function is passed the template directory ($tmpl), the template name (index.tmpl), and a reference to the %item hash. The %item hash is where all of the data from the database is stored so that it can be dynamically added to the HTML pages when the Print_Page function generates them.

Line 19 calls the disconnect function to disconnect the program from the database.

That is all there is to the index.cgi program. Most of the work is handled by the functions in the SmallAuction module, so this program is quite short.

The next program we'll look at is view_all.cgi. This program displays the data in a table format and allows the data to be shown a set number of matches at a time, as shown in Figure 7-2.

click to expand
Figure 7-2: view_all.cgi page

Notice that we again have a drop-down list of items. This gives the user the choice of either clicking an item from the table or selecting an item from the drop-down list.

This program, although it also uses templates, is quite a bit longer than the index.cgi program because there is more going on. The script for this program shows you how to use bind_columns to make your code a bit easier. Using bind_columns is typically a good fit when dealing with a table of data where you are looping and filling in the table. Let's dive into the code and see how it works.

view_all.cgi

start example
01: #!/usr/bin/perl -T 02: # view_all.cgi
end example

Line 1 tells the system where to find Perl and turns on taint checking. Warnings is off here; it was generating an irrelevant message each time the program was executed. Because of this, it was adding a lot of unnecessary data log files. Warnings is an excellent tool to use when creating a program, but when you move the program into a production environment, warnings should be turned off.

Line 2 is simply a comment about this program.

03: use strict; 04: use CGI qw(:standard); 05: use DBI; 06: use lib qw(.); 07: use SmallAuction;

Line 3 loads the strict module so that we have to program more carefully and can avoid common programming mistakes.

Line 4 loads the CGI module and it's standard functions. This is used for CGI programming.

Line 5 loads the DBI module so that we can connect to the database.

Line 6 uses the lib method to add the current directory to the @INC variable.

Line 7 loads the SmallAuction module.

08: $ENV{PATH} = ‘'; 09: my $a_id = 1;   # Auction id number. 10: my $begin_at       = param(‘begin_at') || 0; 11: my $num_to_display = 10;

Line 8 sets the $ENV{PATH} to an empty string. Again, this is to stop some insecure dependency errors that can be caused by using taint checking. Since the $ENV{PATH}

that comes from the shell environment is set outside of this program, taint checking considers it tainted. To overcome this, we can simply set the $ENV{PATH} within the program to whatever we need it to be, "" in this case.

Line 9 declares a scalar variable named $a_id and sets it to 1. This is the ID of the auction we are running.

Line 10 declares a scalar variable named $begin_at and sets it to the value that is passed from the HTML form that calls this page. If no value was passed, the || 0 will set $begin_at to 0.

Line 11 declares a scalar variable named $num_to_display and sets it to 10. This variable is used to determine how many auction items to display on each page.

12: my %item = (); 13: my $tmpl = "templates";

Line 12 declares a hash named %item and makes sure it is empty. This is the hash we'll use to store all of the item information.

Line 13 declares a new scalar variable named $tmpl and sets it to the string "templates". This is the directory that the templates are located in.

14: my $dbh = DBI->connect("dbi:mysql:auction", "bookuser",          "testpass")  15:     or die("Error! $!\nAborting");

Lines 14-15 create our connection to the database. On line 14, we call the DBI->connect method and pass it the database driver name and database name and also the username and password needed to connect to the database.

Line 15 prints an error message and halts the program if there is an error when we connect to the database.

16: $item{item_list}          = Drop_Down_Item_List($dbh,         $a_id, "name"); 17: my ($sth_item, $sth_bids) = Get_Item_Table($dbh,          "name", $begin_at, $num_to_display);

Line 16 calls the Drop_Down_List and passes it the database handle ($dbh), the auction ID ($a_id), and the field to sort the data on ("name"). This function returns a list of items that is formatted so it can simply be dropped into an HTML drop-down list.

Line 17 calls the Get_Item_Table function. This function gets passed the database handle ($dbh), the field to sort the results on ("name"), the number to begin with ($begin_at), and the number of items to display ($num_to_display). By having the beginning number and number of items to display, this makes it very easy for us to list something like five or ten items per page.

The Get_Item_Table function returns two handles ($sth_item, and $sth_bids). These handles contain references to where the data is-just as if we were to create a statement handle for a database query. Doing this makes it very easy for us to get to the data that has been returned.

18: my ($name, $item_id, $descr, $min_bid, $min_incr); 19: $sth_item->bind_columns     (\($name, $item_id, $descr, $min_bid, $min_incr));

Line 18 simply declares several scalar variables we'll be using shortly.

Line 19 is one we haven't seen yet. Notice that we use a new DBI function called bind_columns. This function binds the variables listed; each time we call fetch(), the variables are automatically populated with the current records information. Using this function is very nice because you no longer have to worry about setting the variables each time you fetch data.

20: print header; 21: my $auction_name = Page_Header($dbh, $tmpl, $a_id); 22: Print_Page($tmpl, "view_all.tmpl", \%item);

Line 20 prints the results of the header function. The header function is part of the CGI module. It returns the text that makes up a valid HTTP header.

Line 21 calls the Page_Header function and passes it the database handle ($dbh), the template directory ($tmpl), and the auction ID ($a_id). This function returns a string containing the name of the auction.

Line 22 calls the Print_Page function. This function gets passed the template directory ($tmpl), the name of the template we wish to use ("view_all.tmpl"), and a reference to the %item hash. Remember that the %item hash contains all of the data we need for the current record.

23: my %hi_bid; 24: while(my($amount, $item, $fname, $lname) =        $sth_bids->fetchrow_array){ 25:     $hi_bid{$item}->{name}   = "$fname $lname"; 26:     $hi_bid{$item}->{amount} = "$amount"; 27: }

Line 23 declares a new hash named %hi_bid.

Line 24 begins a while loop that calls fetchrow_array and sets several items to the values it returns. We call the fetchrow_array function here to get the current record.

Line 25 expands the hash to another level. At this level, the bidders first and last name is added to the new key at $hi_bid{$item}->{name}.

Line 26 does the same basic thing as the last line of code contained, except we call this one amount.

Line 27 ends the while loop that begins on line 24.

28: my $color = "e0e0e0"; 29: my $count = 0;

Line 28 declares a new scalar variable named $color and sets it to the string "e0e0e0". We'll be using this value as a way to alternate between colors on the table we display the output on.

Line 29 declares a new scalar variable named $count and initializes it to 0.

30: while($sth_item->fetch){ 31:     my $hi_bid; 32:     $count++; 33:     $color = ($color eq "e0e0e0") ? "ffffff" : "e0e0e0";

Line 30 begins a while loop that keeps fetching data until no more data is left to fetch.

Line 31 declares a new scalar variable named $hi_bid.

Line 32 increments the $count variable. This variable is used to maintain where we are when going through the data.

Line 33 checks to see if the color is equal to e0e0e0. If it is, the first item after the question mark is called-which sets the color to ffffff. If not (the color is currently ffffff), we set the color to the second value after the question mark. This is so that row colors alternate, to make reading easier.

The (foo) ? bar : blech operation on line 33 is called the trinary or ternary operator. It has two names. Use whichever you like. It checks the item on the left of the ? to see if it evaluates to true. If it is true, the first item after the ? is executed. If it is false, the second item is executed.

34:     if(length($descr) > 40){ 35:         $descr  = substr($descr, 0, 37); 36:         $descr .= "..."; 37:     }

Line 34 checks to see if the length of the value in the $descr variable is greater than 40. If so, we want to truncate the data so that we don't have huge item descriptions that would cause our table to look really bad.

Line 35 uses the substr function to take characters 0-37 and store them in the $descr variable. We only want 37 characters because when we append the ellipsis to the string, that will bring it up to our maximum of 40 characters.

Line 36 appends an ellipsis (...) to the end of the string so that the viewers of the page can see that there is more data to read in the description.

Line 37 closes the if block that began on line 34.

38:     $name =      qq(<a href="/cgi-bin/auction/view_item.cgi?item=$item_id" 39:   >$name</a>);

Line 38 sets the $name variable to a link used to call the view_item.cgi script and view a single item.

Line 39 finishes what line 38 begins.

40:     $min_bid  = sprintf("%0.2f", $min_bid); 41:     $min_incr = sprintf("%0.2f", $min_incr); 42:     $hi_bid   = sprintf("%0.2f",              $hi_bid{$item_id}->{amount});

Lines 40-42 use the sprintf function to take the values that are fetched from the database and format them so that they have two decimal places-for the money fields.

The "%0.2f" tells sprintf to format the variable to two places after the decimal. The "f" means this is a floating-point number. This function and its use will be very familiar to C programmers.

43:     print qq( 44:       <tr bgcolor="#$color"> 45:        <td align="left"> 46:         <font >$name</font> 47:        </td> 48:        <td align="center"> 49:         <font >$item_id</font> 50:        </td> 51:        <td align="left"> 52:         <font >$descr</font> 53:        </td> 54:        <td align="center"> 55:         <font >\$$hi_bid</font> 56:        </td> 57:        <td align="center"> 58:         <font >\$$min_bid</font> 59:        </td> 60:        <td align="center"> 61:         <font >\$$min_incr</font> 62:        </td> 63:        <td align="center"> 64:         <font >               $hi_bid{$item_id}->{name}</font> 65:        </td> 66:       </tr> 67:     ); 68: }

Lines 43-67 form a large block that creates the HTML for one table row of data. Notice that the $name, $item_id, $descr, $hi_bid, $min_bid, and $min_incr fields are all variables that are part of the bind_columns function.

Line 68 ends the while loop that begins on line 30. This loop, which runs from line 30 to line 68, continues looping until it runs out of data. Each time through the loop, one row of data is generated.

69: my ($prev, $next, $prev_link, $next_link); 70: $prev = ($begin_at - $num_to_display);

Line 69 declares several scalar variables.

Line 70 creates the number for the $prev variable. This variable is used in the URL to tell the program where to start listing data. $begin_at contains the value we start at, say 50, and $num_to_display contains the number of items we display per page, say 10. In this case, we have 50 - 10 to get a value of 40 for $prev. The $next variable value is derived similarly.

71: $prev_link = qq(&lt; View Prev $num_to_display items);

Line 71 sets the value of $prev_link to a default string. This default string is for when we don't have any previous data to display, so we don't make the value of $prev_link into an HTML link.

72: $prev_link = qq(<b><a href="?begin_at=$prev" 73:         >&lt; View Prev $num_to_display items</a></b>) unless($prev < 0);

Lines 72-73 set $prev_link to a string, as does line 71. This time, however, we are actually setting $prev_link to an HTML link. We get to this section because there is a page to display-so, we make this a link. The unless($prev < 0) at the end tells Perl to only do this string assignment if $previous is NOT less than 0.

74: if($count == $num_to_display) { 75:     $next = ($begin_at + $num_to_display); 76:     $next_link = qq(<b><a href="?begin_at=$next" 77:         >View Next $num_to_display items &gt;</a></b>); 78: } 

Line 74 begins another if..else block. This block checks to see if the value in $count is equal to the value in $num_to_display. If so, we enter the first part of this block.

Line 75 sets $next to the values of $begin_at added to $num_to_display. This is similar to what we do on line 70.

Line 76 creates a string of HTML and sets the $next_link variable to it. This is used to provide a link to the user so that he or she can move to the next page of data, if there is more data.

Line 77 continues what line 76 begins.

Line 78 closes this section of the if..else block.

79: else { 80:    $next_link = qq(View Next $num_to_display items &gt;); 81: }

Line 79 begins the else portion of the if..else block. This block creates the text, but no link, for the $next_link variable.

Line 80 creates the actual text and sets $next_link to it.

Line 81 closes this block.

82: print qq( 83:      </table> 84:     </td> 85:    </tr>     86:    <tr> 87:     <td colspan="2" align="center"> 88:      <font > 89:        $prev_link 90:        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 91:        $next_link 92:      </font> 93:     </td> 94:    </tr> 95: );

Lines 82-95 print the rest of the code required to end the page.

96: Print_Page($tmpl, "footer.tmpl", \%item); 97: $dbh->disconnect; 

Line 96 prints the footer for the page. This calls the Print_Page function, which takes three arguments: the template directory ($tmpl), the template name (footer.tmpl), and a reference to the hash containing the data that is substituted into the template when it is displayed (\%item).

Line 97 disconnects the program from the database.

That is it for the view_all.cgi program. The third, and final, CGI script for our auction program is view_item.cgi. This program is the most complex of the three programs. In this program, we'll be displaying the item information so that it can be bid on. We will also be storing the bids into the database, verifying the data, and viewing a confirmation page! Figure 7-3 shows the view_item.cgi page.

click to expand
Figure 7-3: view_item.cgi page

Let's walk through the code needed to do all of this.

view_item.cgi

start example
01: #!/usr/bin/perl -wT 02: # view_item.cgi
end example

Line 1 tells the system where to find Perl and turns on warnings and Taint checking.

Line 2 is just a comment about this program.

03: use strict; 04: use CGI qw(:standard); 05: use DBI;

Line 3 loads the strict module. Again, use strict in all of your programs.

Line 4 loads the CGI module and its :standard functions.

Line 5 loads the DBI module. We need this module so that we can connect to the database.

06: use lib qw(.); 07: use SmallAuction;

Line 6 adds the current directory, ., to the @INC array so that Perl will look in the current directory for modules.

Line 7 loads the SmallAuction module. This module contains many of the common functions that we will use, and have used, for the auction program.

08: my %item = (); 09: my @err  = ();

Line 8 declares a hash named %item and makes sure it is empty.

Line 9 declares an array named @err and makes sure it is empty.

10: $item{button}  = param(‘bid_button'); 11: $item{$_}      = param($_) 12:     for( qw(item phone fname lname amount minimum) );

Line 10 uses the param function from the CGI module to read in the value passed in the bid_button field.

Lines 11-12 form a single Perl line that reads in the data for each of the parameters named on line 12. It works as follows: The for( qw( ... ) ) on line 12 actually creates a loop, each time through the loop $_ gets set to the current value - item, phone, etc. So as we loop, $_ gets set to the current value. Line 11 uses the $_ to set the names of the hash key and the param value to read. Thus, these lines do exactly the same thing as this:

@foo = qw(item phone fname lname amount minimum); for(@foo){   $item{$_} = param($_); } 

Only the code in the program on lines 11 and 12 does this in a more concise manner.

13: $ENV{PATH} = ‘'; 14: my $a_id   = 1;   ### Auction ID 15: my $tmpl   = "templates";  16: my $count  = 0;

Line 13 sets the $ENV{PATH} environment variable to an empty string. This is needed to satisfy the taint checks. The Date::Manip module will cause errors to be generated if this is not done.

Line 14 declares a scalar variable named $a_id and sets it to 1. This is the auction ID.

Line 15 declares a scalar variable named $tmpl and sets it to the location of the templates directory.

Line 16 declares a scalar variable named $count and sets it to 0.

17: my $dbh = DBI->connect("dbi:mysql:auction", "bookuser",      "testpass")  18:     or die("Error! $!\nAborting");

Line 17 uses the DBI->connect method to connect to the database. A handle to this connection is then stored in the $dbh variable.

Line 18 causes the program to die and to print an error message if the connection to the database fails.

19: my @money = qw(min_bid min_incr value hi_bid needed minimum amount);

Line 19 declares an array named @money and sets it to the values on the right. These fields need to be formatted in a money format.

20: @item{qw(name item_id desc min_bid min_incr value donor hi_bid)} = Get_Item_Details($dbh, $item{item});

Line 20 loads the %item hash with the values of the list passed inside of the curly braces. Don't let the @ fool you; this is actually a hash slice which is equivalent to ($item{‘name'}, $item{‘item_id'}, etc...) = Get_Item_Details(...). Doing this populates the %item hash with the values returned by the Get_Item_Details function.

21: if($item{hi_bid} > 0) { 22:     $item{needed} = $item{hi_bid} + $item{min_incr}; 23: } 

Line 21 begins an if..else block. This block checks to see if the current high bid for the item is greater than 0. If so, we enter this block of code.

Line 22 adds the values in $item{hi_bid} and $item{min_incr} and stores the resulting value in $item{needed}. This creates a variable ($item{needed}), which stores the minimum value needed to place a bid on the item.

Line 23 closes this portion of the block.

24: else { 25:     $item{needed} = $item{min_bid}; 26: }

Line 24 begins the else portion of the block that starts on line 21.

Line 25 sets the $item{needed} value to the value stored in the $item{min_bid} hash element.

Line 26 closes this if..else statement.

27: for my $val (@money) { 28:     $item{$val} = sprintf("%0.2f", $item{$val}); 29: }

Line 27 begins a for loop that traverses through the @money array. Each time through, it sets the variable $val to the current value.

Line 28 uses the sprintf function to format the value that is currently in $item{$val} and makes sure that it contains two decimal places.

We are looping through the @money array because it contains all of the keys to the values that should be in a monetary format.

Line 29 closes this for loop.

30: print header; 31: my $auction_name  = Page_Header($dbh, $tmpl, $a_id); 32: my $empty_form    = 1; 33: $item{item_list}  = Drop_Down_Item_List($dbh, $a_id, "name");

Line 30 prints the value returned by a call to CGI's header function.

Line 31declares a variable named $auction_name and sets it to the value returned by a call to the Page_Header function. Page_Header is passed three variables-a handle to the database ($dbh), the directory that the templates are in ($tmpl), and the auction ID ($a_id).

Line 32 declares a scalar variable named $empty_form and sets it to 1. You will see where this is used in a bit-we'll basically be using it as a boolean switch to check form status.

Line 33 sets the $item{item_list} value to the data returned by a call to the Drop_Down_Item_List function. The "name" item you see being passed to the subroutine is the name of the field that we want the results sorted on.

34: if($item{button} eq ‘Submit Bid'){ 35:     my $rval = Bidding_Open($dbh, $a_id);

Line 34 checks to see if the value in $item{button} is equal to ‘Submit Bid'. If so, the user must be trying to submit a bid, so this block of code is entered.

Line 35 declares a variable named $rval and sets it to the data returned by the Bidding_Open subroutine. The Bidding_Open function takes a handle to the database ($dbh) and the auction ID ($a_id). This function checks to see if bidding is allowed. It returns 0 for no or a different value if bidding is allowed.

36:     unless($rval) { 37:         Print_Page($tmpl, "closed.tmpl", \%item); 38:         Print_Page($tmpl, "footer.tmpl", \%item); 39:         exit; 40:     }

Line 36 checks $rval to see if it is 0. The unless function in Perl means "if NOT." So, this line is checking to see if $rval is NOT true-0 is NOT true, so this block gets entered if bidding is not allowed.

Line 37 prints the closed_tmpl template to let the bidder know that the auction is currently closed.

Line 38 prints the footer.tmpl, which is just the code to finish the HTML page.

Line 39 exits the program. We absolutely don't want the user to go any further in the program if bidding is closed-this makes sure of that.

Line 40 closes the unless block, which begins on line 36.

41:     @err          = Check_Data(); 42:     $item{errors} = join(‘<br />', @err); 43:     $empty_form   = 0; 44: }

Line 41 calls the Check_Data function and stores the results into the @err array.

Line 42 uses the join function to join all of the items in the @err array with the <br /> HTML tag and to store the result of this action in the variable $item{errors}.

Line 43 sets the $empty_form variable to 0. This will be used to tell the program that the form is not empty.

Line 44 closes this block, which begins on line 34.

45: if(@err or $empty_form){ 46:     Print_Page($tmpl, "view_item.tmpl", \%item); 47: }

Line 45 checks to see if @err or $empty form are true. If @err has any data in it, or if $empty_form is not 0, this block gets entered.

Line 46 uses the Print_Page to print the view item page. This function gets the template directory ($tmpl), the template file name (view_item.tmpl), and the items to populate the page with (\%item) passed to it.

Line 47 closes the if portion of this block.

48: else { 49:     Submit_Bid($dbh, \%item); 50:     Print_Page($tmpl, "confirmation.tmpl", \%item); 51: }

Line 48 is the else portion. If there are no errors and the form is not empty, we enter this block.

Line 49 calls the Submit_Bid function. This function gets passed a handle to the database ($dbh) and a reference to the item hash (\%item). This adds the bid to the database.

Line 50 calls the Print_Page subroutine. This call prints the bid confirmation page so that the bidder knows that his or her bid has been accepted.

Line 51 closes the if..else block.

52: Print_Page($tmpl, "footer.tmpl", \%item); 53: $dbh->disconnect;

Line 52 calls the Print_Page subroutine to print the page footer. This prints the ending HTML so that we don't forget any closing tags.

Line 53 disconnects us from the database. This is really the end of the program; the rest of the lines are the subroutines we call.

54: sub Check_Data { 55:     my @err = (); 56:     my %bid = ( 

Line 54 begins the Check_Data subroutine. This subroutine is used to validate the data that the bidder enters. We want to make sure that the bidder is not trying to pull anything funny on us.

Line 55 declares the @err array and makes sure it is empty.

Line 56 declares the %bid hash and opens the parentheses. We are going to load this variable up with a bunch of data that tells the program how to filter the data passed to it. The %bid hash is going to end up being a hash of hashes.

Each key value corresponds to a field value on the HTML form.

57:   phone  => { value    => $item{phone}, 58:               required => 1, 59:               filter   => ‘PHONE', 60:               error    => ‘Error with the phone number.', 61:             },

Line 57 creates a hash with the key of phone. This is the %bid{phone} hash. We set several values here. The value key stores the actual data.

Line 58, the required key, is used to determine if this is a required field or not.

The preceding required key is not really put to use in this application, since we are treating all fields as required. It was left in, though, because if you want to have more fields and make some required and others not, this is a very easy way to enforce that. Simply set required to either 1 or 0, and check to see if the value in value contains any data.

Line 59 sets filter to a data type we'll filter on.

Line 60 stores the error message that gets pushed into the @err array if something is wrong.

Line 61 ends the phone item.

62:   amount => { value    => $item{amount}, 63:                     required => 1, 64:                     filter   => ‘CURRENCY', 65:                     error    => ‘Error with the amount.', 66:                     error2   => ‘Bid too low.', 67:                   },

Lines 62-67 create the information needed to validate the amount field. Notice that this one has two error keys (error and error2). This is done so that we can show more descriptive error messages to the auction bidder.

68:   fname  => { value    => $item{fname}, 69:               required => 1, 70:               filter   => ‘TEXT', 71:               error    => ‘Error with the First Name.', 72:             }, 73:   lname  => { value    => $item{lname}, 74:               required => 1, 75:               filter   => ‘TEXT', 76:               error    => ‘Error with the Last Name.', 77:             }, 78:   );

Lines 68-77 create the information needed to validate the fname and lname fields.

Line 78 closes this hash declaration, which begins on line 56.

79:     while(my($k,$v) = each %bid) {

Line 79 begins a while loop that iterates through the %bid hash. For each item, the current hash element and the key and value are stored in $k and $v, respectively.

80:         if($v->{filter} eq ‘TEXT') { 81:          push @err, $v->{error}  82:              unless($v->{value} =~ /^[\w \.\']+$/); 83:         }

First, notice that we are using $v, the value, to reference all of these hashes. This is because %bid is a hash of hashes. For each key, the value is also a hash.

Line 80 begins the filter for all items that are set to the TEXT filter type. If the filter hash element is equal to TEXT, we enter this block.

Line 81 pushes the value in $v->{error} onto the @err array if it does not pass the test on line 82.

Line 82 is really a continuation of line 81. This line checks to see if the data in $v->{value} matches the regular expression on the right.

Let's take a closer look at what this regular expression is doing:

/^[\w \.\']+$/ breaks down to this.

The ^ anchors this regular expression to the beginning of the string. This means we are checking from the very beginning of the string; nothing can exist first. The items in the [] are a character class. A character class means that any of the items in that character class are allowed to match.

Inside the character class, the following conditions exist:

  • The \w is the shortcut to look for word characters. Word characters are the letters, numbers, and underscore.

  • Then there is a space; you can't really see it, but there is a physical space there.

  • Next is the period \. . The period must be escaped in a regular expression-an unescaped period in a regular expression matches any character.

  • Next is the single quote \'. This is also escaped.

We close the character class with the ]. The + following the closing ] means that the characters can match one or more times.

We have the $, which anchors this regular expression to the end of the string. By using both the ^ at the beginning and the $ at the end of the string, we anchor this regular expression to the beginning and end of the string. If we don't anchor the regular expression and it validates the data anywhere in the string, it considers that to be a successful match.

After all of that, it boils down to this: We check to make sure that the TEXT data type contains only letters, numbers, the underscore, space, period or a single quote.

Line 83 closes the if block.

84:         elsif($v->{filter} eq ‘CURRENCY') { 85:         push @err, $v->{error}  86:             unless($v->{value} =~ /^\$?[\d\.\,]+$/); 87:         push @err, $v->{error2}  88:             if($item{needed} > $item{amount}); 89:         }

Line 84 is where we go to check for items that are of the CURRENCY type.

Line 85 pushes the value in $v->{error} onto the @err array if the regular expression on line 86 is not passed.

Line 86 checks the data to see if it contains only the data in the regular expression. This regular expression is similar to the previous one. We again have the ^ and $ to anchor the string.

The \$? means look for a dollar sign (we escape this dollar sign). If it is present, that is okay, but it does not have to be there. The question mark means that the character(s) can be there, but they don't have to be.

The \d means that it matches digits. Also, we have added the comma and removed the single quote and space.

Line 87 pushes the value in $v->{error2} if the condition on line 88 is met.

Line 88 checks to see if the value in $item{needed} is greater than the value in $item{amount}.

Line 89 closes this part of the if..elsif code.

90:         elsif($v->{filter} eq ‘PHONE') { 91:         push @err, $v->{error}  92:             unless($v->{value} =~ /^[\d \.\-\(\)]{1,20}$/); 93:     } 94:     }

Line 90 is where we go to check for items that are of the PHONE type.

Line 91 pushes the value in $v->{error} onto the @err array if line 92 returns true.

Line 92 is true if the test of the regular expression against the value in $v->{value} is true. This means we are performing an if NOT test. If the value contains only the items in the regular expression, there is no error with the data, so we don't want to push an error message onto the @err array.

This regular expression checks for digits, the space, period, dash and parentheses. The regular expression also limits the number of characters to 20. If the string contains characters other than these, or is blank, the test will fail, and the error message will get set.

Line 93 closes this part of the if..elsif block.

Line 94 closes the while loop that begins on line 79.

95:     return(@err); 96: }

Line 95 returns the @err array to the calling line of code.

Line 96 ends this subroutine.

And that wraps up the view_item CGI program. The final part of our SmallAuction application is the actual Perl module that we have many of the common subroutines in. Using a module like this one is a great way to keep all of your common subroutines together that you can avoid having redundant code.

SmallAuction.pm

start example
01: package SmallAuction; 02: use strict; 03: use Date::Manip qw(UnixDate Date_Cmp);
end example

Line 1 uses the package function to tell Perl that this is now the SmallAuction namespace.

Line 2 loads strict; we use strict in all of our programs.

Line 3 loads the Date::Manip module and imports the UnixDate and Date_Cmp functions.

04: use vars qw(@ISA @EXPORT); 05: use Exporter;

Line 4 declares @ISA and @EXPORT as global variables.

Line 5 loads the Exporter module.

06: @ISA = qw(Exporter); 07: @EXPORT = qw(Page_Header Drop_Down_Item_List Print_Page 08:              Bidding_Open Get_Item_Details Submit_Bid 09:              Get_Item_Table);

Line 6 adds Exporter to the @ISA array. This tells Perl that this module is going to have the ability to export functions.

Lines 7-9 push a list of functions to export onto the @EXPORT array. Any of the functions you want to be able to call from other programs using this module must be listed here.

10: sub Print_Page { 11:     my $tmpl = shift; 12:     my $page = shift; 13:     my $item = shift;

Line 10 begins the Print_Page subroutine.

Lines 11-13 declare some variables and use the shift function to populate them with the data that passed into the subroutine.

14:  open(TMPL, "$tmpl/$page") or die("ERROR: $!\nAborting");

Line 14 opens the $page file in the $tmpl directory. If there is an error with the open function, the die function is called, and an error message is displayed.

15:         while(<TMPL>) { 16:             s/%%(\w+)%%/$item->{$1}/g; 17:             print; 18:         }

Line 15 begins a while loop. This loop uses the file handle we create on line 14. This causes this block to loop through every line of the file.

Line 16 looks for elements surrounded by %%element%%. If it finds an element, it replaces the element with whatever is in the %item hash and uses the value between the %%'s as the key. This allows us to put elements into the template, having them dynamically replaced with the proper data.

Line 17 prints the current line.

Line 18 ends this while loop.

19:     close(TMPL); 20:     return; 21: }

Line 19 closes the TMPL file handle.

Line 20 returns from this function.

Line 21 ends this function.

22: sub Page_Header { 23:     my $dbh  = shift; 24:     my $tmpl = shift; 25:     my $a_id = shift;

Line 22 begins the Page_Header subroutine.

Lines 23-25 use the shift function to fetch the items passed in the function call to this function.

26:     my ($start, $stop, %item); 27:     my @format = qw(%A, %B %E at %i:%M%p %Z);

Line 26 declares some variables we'll use later on.

Line 27 sets the format for the data/time. This is needed for the Date::Manip module.

28:    my $sql  = qq(SELECT name, start_bidding, stop_bidding 29:                  FROM auction WHERE auction_id = ?);

Lines 28-29 create the SQL statement needed to get the name of the auction and the start and stop bidding times.

30:     my $sth  = $dbh->prepare($sql); 31:     $sth->execute($a_id);

Line 30 prepares the SQL statement and stores the result in the $sth statement handle.

Line 31 executes the SQL statement and passes the auction ID ($a_id) to the SQL.

32:     ($item{auction}, $start, $stop) =            $sth->fetchrow_array; 33:     $item{start_date} = UnixDate($start, @format); 34:     $item{stop_date}  = UnixDate($stop, @format); 

Line 32 fetches a row of data from the query executed previously. The results are stored in the variables on the left.

Lines 33-34 formats the date/time elements we get from the database.

35:     Print_Page($tmpl, "header.tmpl", \%item); 36:     return($item{auction}); 37: }

Line 35 calls the Print_Page subroutine, which prints the page header.

Line 36 returns the $item{auction} value to the calling subroutine.

Line 37 ends this subroutine.

38: sub Is_Bidding_Open { 39:     my $dbh  = shift; 40:     my $a_id = shift;

Line 38 begins the Is_Bidding_Open function.

Lines 39-40 declare two variables and set them to the values passed to this subroutine.

41:     my $sql  = qq(SELECT start_bidding, stop_bidding 42:                   FROM auction WHERE auction_id = ?);

Lines 41-42 create an SQL statement that we'll use in a moment.

43:     my $sth  = $dbh->prepare($sql); 44:     $sth->execute($a_id);

Line 43 prepares the SQL statement and stores a handle to the prepared statement in your phone.

Line 44 calls the execute function.

45:     my ($start, $stop) = $sth->fetchrow_array;

Line 45 fetches the start time and stop time for the auction and stores them in the $start and $stop variables.

46:     return 0 if Date_Cmp("today", $start) == -1;  47:     return 0 if Date_Cmp($stop, "today") == -1; 

Lines 46-47 return 0 if the value returned by the call to Date_Cmp is -1, a result of -1 means that the date comparison failed.

48:     return 1; 49: } 

Line 48 returns 1; if lines 46 or 47 did not yet return a 0, then we must be okay so we return a 1.

Line 49 ends the Bidding_Open subroutine.

50: sub Drop_Down_Item_List { 51:     my $dbh     = shift; 52:     my $auction = shift; 53:     my $sorted  = shift;

Line 50 begins the Drop_Down_Item_List subroutine. This subroutine fetches the item list and creates the HTML needed for a drop-down list.

Lines 51-53 declare some scalar variables and use the shift function to read in the values passed to this function.

54:     my $sql     = qq(SELECT item_id, name FROM item WHERE  55:                      auction_id = ?); 56:      $sql .= " ORDER BY $sorted" if $sorted;

Lines 54-55 make up the SQL statement we need to get the data we are looking for. We are selecting the item_id and name from the item table WHERE auction id matches that value passed in the execute command.

Line 56 appends ORDER BY $sorted if a value for sorting on is passed to this function when it is called. This helps us generate the SQL query statement dynamically.

57:     my $sth     = $dbh->prepare($sql); 58:     my $options = undef; 59:     $sth->execute($auction);

Line 57 uses the prepare method on the SQL statement and stores the result in the newly declared variable, $sth. By preparing the SQL, you store the database calls into a precompiled, optimized form. Not all databases take true advantage of this feature, but it needs to be called nonetheless before you execute the statement.

Line 58 declares a new variable named $options and initializes it to undef.

Line 59 calls the execute method on the $sth handle and passes the value in $auction. This value then gets substituted in place of the placeholder (?) in the SQL statement.

60:     while(my $p = $sth->fetch) { 61:        $options .=                qq(<option value="$p->[0]">$p->[1]</option>\n); 62:     } 

Line 60 begins a while loop that continues looping as long as data is being returned by the $sth->fetch. Each time the fetch method is called, the result is stored in $p, which ends up being a scalar variable that stores a reference to an array.

Line 61 takes the $options variable by using the .= operator.

There is really no technical term for the .= assignment operator. After much discussion, we've decided to call it the "appendinate" operator. appendinate is appropriate because the . is the concatenation operator. Together with the =, it concatenates and appends all at once.

By using the appendinate operator, we are accomplishing the same thing as a push onto an array, except we are "pushing" onto a scalar. This simply means that when we are done, the $options scalar will contain a potentially large string of data that makes up the drop-down box of auction items.

The $p->[0] is the item_id, and $p->[1] is the item name.

Line 62 closes the while loop that begins on line 60.

63:     return($options); 64: }

Line 63 returns the $options variable.

Line 64 ends this function.

65: sub Submit_Bid { 66:     my $dbh     = shift; 67:     my $item    = shift;

Line 65 begins the Submit_Bid subroutine. This subroutine is used to-you guessed it-submit a bid.

Lines 66-67 declare some scalar variables and use the shift function to initialize them to the values passed to this subroutine when it is called.

68:     my $sql_bid = qq(INSERT INTO bids  69:                      (item_id, amount, first_name,  70:               last_name, phone)  71:                      VALUES (?, ?, ?, ?, ?));

Lines 68-71 create an SQL statement that we use to insert a record into the bids table.

Line 68 declares the $sql_bid variable; then we use the qq function to create the SQL string.

Line 71 uses several placeholders (?); this is where the data is dynamically inserted into the statement when it is executed.

72:     my $sth_bid = $dbh->prepare($sql_bid); 73:     $sth_bid->execute(                    @$item{ qw(item amount fname lname phone) }         );

Line 72 declares a scalar named $sth_bid and sets it to the value returned by the prepare method call. The sth_bid stands for StatemenT Handle for the bid SQL statement.

Line 73 executes the SQL statement by calling the execute function. The values passed into the execute function are done by using a hash slice. Since the motto of Perl is "There Is More Than One Way To Do It" (TIMTOWTDI, for short), we choose to do things a bit differently here.

This code acts the exact same way as this code:

$sth_bid->execute($item->{item},  $item->{amount},                    $item->{fname}, $item->{lname},                   $item->{phone});

But as you can see, our way of doing it is much shorter. Since the $item hash contains all of the data we need, and we need only a subset of that data, a hash slice makes good sense.

Note 

The @ can be a little confusing because it looks like "array"; what matters here though is the {} rather than the []. The documentation at perldoc perldata is quite good at explaining this. It says to consider the @ to be like these or those in English. So, it is like reading it these hash values.

Getting back to our %item hash, we want the item, amount, fname, lname and phone from it. We can quote each one like so:

 @$item{ "item", "amount", "fname", "lname", "phone" }

But that leaves a lot of room for typing errors. Instead, we use the 'quote word' function (qw). qw passes the items in a list, just like previously, but via a much cleaner method-leaving less room for error.

74:     return; 75: }

Line 74 returns us from this subroutine.

Line 75 closes the subroutine.

76: sub Get_Item_Details { 77:     my $dbh     = shift; 78:     my $item_id = shift;

Line 76 begins the Get_Item_Details subroutine. This subroutine is used to get all of the information needed to display an "item" page, which is where users go to bid on an item.

Lines 77-78 declare some scalar variables and use the shift function to initialize them to the data passed to the subroutine.

79:     my $sql_item  =  80:         qq(SELECT  81:         a.name, a.item_id, a.description,  82:             a.min_bid, a.min_incr, a.value, b.name 83:        FROM  84:         item AS a, donor AS b 85:        WHERE  86:         a.item_id = ?  87:        AND  88:         a.donor_id = b.donor_id 89:           );

Lines 79-89 make up the SQL statement to get the item information we need. It is on several lines simply to make it more readable. Nothing is worse than looking at someone's code and seeing a huge blob of text. Spread things, indent, and make generating clean code one of your goals.

Line 80 begins the SQL statement by opening a block of text with the qq function. qq is equivalent to the double quote. There is also a q operator that is equivalent to the single quote. Remember that a double-quoted string interpolates the variables within it; a single-quoted string does not.

Lines 81-82 list the fields we want to get data for. Notice that they have an a. and a b. in front of the field names. We are selecting from multiple tables and these allow us, and the database, to know which table data we are referring to.

Line 84 lists the tables we are querying in. The AS a and AS b are where we get the a. and b. on lines 81 and 82. Otherwise we would have to explicitly name the tables using their full names (item and donor).

Line 86 makes sure that we get to right item_id.

Line 88 makes sure that the donor_id in both tables matches.

Line 89 closes the qq block that begins on line 79.

90:     my $sql_bids  =  91:         qq(SELECT MAX(amount) 92:             FROM bids  93:            WHERE item_id = ? );

Line 90 declares a variable named $sql_bids. This variable contains the text that makes up the SQL statement to get the amount of the highest bid for the item we are looking at.

Line 91 uses the qq function to create the string. Here we are using the database server's MAX function to get the largest value in the amount field for the items that are returned.

Line 93 uses a placeholder to limit our search. We are searching on the item_id field here. Although there may be many bids on that item, only one can be the MAX bid.

94:     my $sth_item  = $dbh->prepare($sql_item); 95:     my $sth_bids  = $dbh->prepare($sql_bids);

Line 94 declares a variable named $sth_item and prepares the $sth_item SQL statement. $sth_item holds a handle to the prepared statement when finished.

Line 95 declares a variable named $sth_bids and prepares the $sth_bids SQL statement. $sth_bids holds a handle to the prepared statement when finished.

96:     $sth_item->execute($item_id); 97:     $sth_bids->execute($item_id);

Lines 96-97 execute their respective SQL statements.

98:     my @data = $sth_item->fetchrow_array; 99:     my $temp = $sth_bids->fetchrow_array;

Line 98 uses the fetchrow_array method to get the data for the current item. There are several pieces of data coming back, so we store the results into an array.

Line 99 also uses the fetchrow_array method, but only one value is being returned, so we store the results into a scalar variable instead of an array.

100:     push @data, $temp; 101:     return(@data); 102: }

Line 100 uses the push function to add the value in $temp to the @data array.

Line 101 returns the @data array.

Line 102 closes this subroutine.

103: sub Get_Item_Table{ 104:     my $dbh       = shift; 105:     my $sort      = shift; 106:     my $start_at  = shift; 107:     my $count     = shift;

Line 103 begins the Get_Item_Table subroutine. This subroutine is used to get a list of all of the items so that they can be displayed in a table.

Lines 104-107 declare scalar variables and use the shift function to initialize them with the data passed to the subroutine.

108:     $start_at = 0 unless $start_at; 109:     $count    = 5 unless $count;

Line 108 sets the value of $start_at to 0, unless $start_at already contains some data. We use the $start_at variable to determine where to start displaying data. If no starting point is sent, we set it to 0 here as the default.

Line 109 sets the value of $count to 5 unless it already contains something. We use this variable to determine how many items to display on a page.

110:    my $sql_item  = qq(SELECT name, item_id, description,  111:                       min_bid, min_incr FROM item  112:                 ORDER BY $sort LIMIT $start_at, $count);

Lines 110-112 declare a new scalar variable named $sql_item and create a string of SQL that gets stored in it. Notice that we use variables, not placeholders, in the SQL on line 112. This is because a placeholder cannot be used in place of the $start_at and $count variables. There is no point in using a placeholder for the $sort value but not the others.

113:     my $sth_item  = $dbh->prepare($sql_item);

Line 113 prepares the $stl_item SQL statement and stores the result of this, a handle, in the new scalar variable named $sth_item.

114:     $dbh->do("CREATE TEMPORARY TABLE                     tmp(amount double, item_id int)");

Line 114 creates a new table that we'll use temporarily to hold some data. MySQL does not support subselects (that is, a SELECT statement within a select statement). To get around this, you can create a temporary table to store the data in and then use it as part of a query.

The temporary table we are creating here only contains two fields: amount and item_id.

115:     $dbh->do("LOCK TABLES bids, item read"); 

Line 115 locks out the bids table and item table. This is so we don't have people adding data when we are trying to select it.

116:     $dbh->do("INSERT INTO tmp SELECT MAX(amount),  117:               item_id FROM bids GROUP BY item_id");

Lines 116-117 are the SQL needed to insert the maximum amount and item_id from the bids table and insert them into the tmp table. This takes the place of our subselect.

There is a good reason why this has to be done: to get the high bid and high bidders from the bids table, a simple enough task-on the surface.

      SELECT item_id, last_name, MAX(amount) FROM bids GROUP BY item_id;

This appears to work and even returns data that looks perfectly valid. You get the proper item_id and MAX(amount) values without a problem. But the last_name is unpredictable! The reasons for this are complex and documented in MySQL. It all boils down to the fact that a subselect needs to be used to handle this task. Since MySQL doesn't support subselects, we'll use a temporary table.

118:    my $sql_bids = qq(SELECT bids.amount, bids.item_id,             first_name,  119:       last_name FROM bids, tmp WHERE  120:       bids.amount=tmp.amount AND 121:       bids.item_id=tmp.item_id);

Lines 118-121 make up the SQL needed to get the data we are looking for. The SQL statement is stored in a new scalar variable named $sql_bids. This query selects data from the bids table and the tmp table. It uses the tmp table to ensure that we get the right data.

122:     my $sth_bids  = $dbh->prepare($sql_bids); 123:     $sth_item->execute; 124:     $sth_bids->execute;

Line 122 prepares the $sql_bids query and stores a handle to the prepared query in $sth_bids.

Lines 123-124 execute the $sth_item and $sth_bids queries.

125:     $dbh->do("DROP TABLE tmp"); 126:     $dbh->do("UNLOCK TABLES");

Line 125 DROPs (deletes) the tmp table.

Line 126 unlocks the tables.

127:     return($sth_item, $sth_bids); 128: } 129: 1;

Line 127 returns the $sth_item and $sth_bids statement handles.

Line 128 closes this subroutine.

Line 129 is needed because all modules must return a true value-this ensures that by returning a 1.



Perl Database Programming
Perl Database Programming
ISBN: 0764549561
EAN: 2147483647
Year: 2001
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net