Consider now what happens if the user reloads the success message page with the Reload or Refresh button in the browser. Unfortunately, the variables and values are resubmitted to the same script, and another region row-with the same name and description-is added to the region table. There is no way in this example that the first click of the Submit button to add the first row can be distinguished from a second action that sends the same variables and values to the script. A representation of the reload problem is shown in Figure 6-1.
Figure 6-1. The reload problem
The same reload problem occurs when the user stores the URL as a bookmark or favorite location in her browser and then later requests the URL. Other actions that return to the success page, such as using the Back button, have the same undesirable effect. Perhaps surprisingly, resizing the browser window or printing the page also creates a new HTTP request and causes the reload problem. In our case, each request for the URL adds another identical region to the winestore!
|
The reload problem isn't always a significant problem. For example, if you use the SQL UPDATE
statement to update customer details, and the values are amended with the same correct values repeatedly, there is no data duplication. Indeed, if a row is deleted and the user repeats the operation, the user, at worst, sees a MySQL DBMS error message. However, while some UPDATE
and DELETE
operations are less susceptible to the reload problem, a well-designed system avoids the problem altogether. Avoidance prevents user confusion and unnecessary DBMS activity. We discuss a solution in a moment.
The HTTP POST
method is a little less susceptible to the reload problem than the GET
method. If a user reretrieves the script after the first database change, the browser should ask the user whether or not to repost form data as per the HTTP specification. If the user answers OK
, the database operation is repeated causing the problem. However, if the user bookmarks the page or reenters the URL at a later time, the <form>
is redisplayed because the POST
variables and values aren't part of the URL and are lost.
A solution to the reload problem is shown in Figure 6-2, based on the HTTP Location:
header, the same header used for one-component querying in Chapter 5.
Figure 6-2. Solving the reload problem with a redirection to a receipt page
The reload solution works as follows:
-
The user submits the
<form>
with the variables and values for a database write operation (an SQLINSERT
,UPDATE
, orDELETE
). -
The SQL write operation is attempted.
-
Whether or not the modification is successful, an HTTP
Location:
header is sent to the browser to redirect the browser to a new, receipt page.HTTP
GET
encoded variables and values are usually included with theLocation:
header to indicate whether the action was successful or not. Additionally, text to display might be sent as part of the redirection URL. -
An informative-but harmless-receipt page is displayed to the user, including a success or failure message, and other appropriate text.
The HTTP redirection solves the reload problem. If the user reloads the receipt page the browser has been redirected to, he sees the receipt again, and no database write operations occur. Moreover, since the receipt page receives information about the success or failure of the operation-and any other information identifying the action-encoded in the URL, the receipt page URL can be saved and reloaded in the future without any undesirable effect.
Solving the reload problem in practice
A modified version of Example 6-1 with the redirect functionality is shown in Example 6-2. The code is almost identical to that of Example 6-1, with two exceptions.
The first difference in the script in Example 6-2 is that regardless of whether the database insert succeeds or fails, the header( )
function is called. This redirects the browser to the script shown in Example 6-3 by sending a Location:
example.6-3.php
HTTP header. The difference between the success and failure cases is what is appended to the URL as a query string. In the case of success, status=T
and the value of the added region_id
attribute are sent. A value of status=F
is sent on failure.
The second difference is that the script allows the user to upload a map of the wine region in GIF format for storage in the database. We discuss this functionality in the next section. The script also uses the function mysql_insert_id( )
; look for this function in the later section Section 6.1.3.
Example 6-2. An insertion script
<?php include 'db.inc'; include 'error.inc'; if (empty($regionName) || empty($description)) { ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Insert a Region</title> </head> <body> <form enctype="multipart/form-data" action="example.6-2.php" method="post"> Region_name: <br><input type="text" name="regionName" size=80> <br>Description: <br><textarea name="description" rows=4 cols=80> </textarea> <input type="hidden" name="MAX_FILE_SIZE" value="100000"> <br>Region map (GIF format): <input name="userfile" type="file"> <br><input type="submit"> </form> </body> </html> <?php } else { $regionName = clean($regionName, 50); $description = clean($description, 2048); if (!($connection = @ mysql_connect($hostName, $username, $password))) die("Could not connect to database"); if (!mysql_select_db($databaseName, $connection)) showerror( ); // Was an image file uploaded? if (is_uploaded_file($userfile)) { // Open the uploaded file $file = fopen($userfile, "r"); // Read in the uploaded file $fileContents = fread($file, filesize($userfile)); // Escape special characters in the file $fileContents = AddSlashes($fileContents); } else $fileContents = NULL; // Insert region data, including the image file $insertQuery = "INSERT INTO region VALUES (NULL, " . "\"" . $regionName . "\", " . "\"" . $description . "\", " . "\"" . $fileContents . "\")"; if ((@ mysql_query ($insertQuery, $connection)) && @ mysql_affected_rows( ) == 1) header("Location: example.6-3.php?" . "regionId=". mysql_insert_id($connection) . "&status=T"); else header("Location: example.6-3.php?" . "status=F"); } ?>
The script in Example 6-3 is a receipt page. When requested with a parameter status=T
, it queries the database and displays the details of the newly inserted region. The region is identified by the value of the query string variable regionId
. The script also uses another script to display the image of the map inserted by the user; this approach is discussed next. On failure, where status=F
, the script displays a database insertion failure message. If the script is unexpectedly called without a status
parameter, an error message is displayed.
Example 6-3. The redirection receipt page
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Region Receipt</title> </head> <body bgcolor="white"> <?php include 'db.inc'; include 'error.inc'; $regionId = clean($regionId, 3); $status = clean($status, 1); // did the insert operation succeed? switch ($status) { case "T": // Yes, insert operation succeeded. // Show details of the new region as // a receipt page. The new region_id // is in the variable $regionId $query = "SELECT * FROM region WHERE " . "region_id = $regionId"; // Connect to the MySQL DBMS if (!($connection = @ mysql_connect($hostName, $username, $password))) die("Could not connect to database"); if (!mysql_select_db($databaseName, $connection)) showerror( ); // Run the query on the DBMS if (!($result = @ mysql_query ($query, $connection))) showerror( ); if ($row = @ mysql_fetch_array($result)) { echo "The following region was added"; echo "\n<br>Region number: " . $row["region_id"]; echo "\n<br>Region name: " . $row["region_name"]; echo "\n<br>Region description: " . $row["description"]; // Use the script example.6-4.php to display // the map GIF echo "\n<br>Region map : " . "\n<br><img src=\"example.6-4.php?region_id=" . $regionId . "\">"; } // if mysql_fetch_array( ) // leave the switch statement break; case "F": // No, insert operation failed // Show an error message echo "The region insert operation failed."; echo "<br>Contact the winestore administrator."; // leave the switch statement break; default: // User did not provide a status parameter echo "You arrived unexpectedly at this page."; } // end of switch ?> </body> </html>
Several different receipt pages would be developed for an application to informatively display enough information for each different insert, update, and delete operation.