Web Database Applications with PHP & MySQL By Hugh E. Williams, David Lane This errata page lists errors outstanding in the most recent printing. If you have technical questions or error reports, you can send them to booktech@oreilly.com. Please specify the printing date of your copy. This page was updated March 21, 2006. Here's a key to the markup: [page-number]: serious technical mistake {page-number}: minor technical mistake : important language/formatting problem (page-number): language change or minor formatting problem ?page-number?: reader question or request for clarification Confirmed errors: {43} discussion '=' vs '==' about 1/2 way down; In this quote "The incorrectly formed conditional expression ($var = 1) always evaluates as true,because the assignment that actually occurs always succeeds and, therefore, is always true." The implication is that any c.e. of this form will always evaluate to TRUE because it's the assignment that matters. In fact it's the value on the right hand side of the '=' that matters. If the '1' had been a '0', then the c.e. would be FALSE. It seems to work the same as it does in perl where I have often used this ideom to both assign and test the return value of a function. {41} The 4th if statment on the page reads... if ($var > 5) { echo "Variable is less than 5"; echo "-----------------------"; } else { echo "Variable is equal to or larger than 5"; echo "-----------------------"; } it should read... if ($var > 5) { echo "Variable is larger than 5"; echo "-----------------------"; } else { echo "Variable is equal to or less than 5"; echo "-----------------------"; } {43} last paragraph; if ($var == 3) || ($var == 7) echo "Equals 3 or 7"; it should be like this if (($var == 3) || ($var == 7)) echo "Equals 3 or 7"; (48) top page, fragment code, line 7 (code line); the code reads: $inch=(100*$cm)/2.45; should be $inch = $cm / 2.54; (54) In the code block under Example 2-4; The fragment "Venus" => array("dist"=>0.39 should be "Venus" => array("dist"=>0.72. (57) top $item = $index + 1; echo $index + 1 . ". $cm centimeters = $inch inches\n"; should be: $item = $index + 1; echo $item . ". $cm centimeters = $inch inches\n"; [57] code sample after 1st full paragraph; the print_r functions are missing parentheses. print_r each($a); should be print_r(each($a)); (63) IN PRINT: 2nd paragraph; "When assort()..." SHOULD BE: "When asort()..." (67) Last paragraph; A space is missing between the format string and the descriptive text on the first line. The text currently reads: The format string Result: %.2f\nis the first... It should read: The format string Result: %.2f\n is the first... (72) bottom of last example; "onlyprint strrpos($var, "Zoo");" remove the 'only' (74) 1st example; The last line of the code is missing the semicolon. It currently reads: echo "Guest list: " . implode(", ", $guestArray) It should read: echo "Guest list: " . implode(", ", $guestArray); {83} first echo statement; // prints "The quick brown fox" should be: // prints "The quick brown fox jumps" {92/93} 92-bottom / 93 - top; switch($headinglevel) { <= missing [snip] } <= missing (97) IN PRINT: heading function; $headingLevel should be $level (97) 2nd paragraph; the switch statement is missing a pair of {}: Reads line 7: switch($level) case 1: ... Should read line 7: switch($level) { case 1: ... } (115) Figure 3-3; Most of Figure 3-3 is missing. However, the correct figure is also shown as Figure C-6 on page 517. {116} 1st and 2nd create table query; example 3-1 In the CREATE TABLE wine query - there should be a comma , after KEY name (wine_name) In the CREATE TABLE winery query - there should be a comma , after KEY name(winery_name) (117) middle; The PRIMARY KEY for the orders and items tables includes "order_no", which is not a field in either table and is not defined. It should read "order_id" in both cases, which is a field and is thus defined. This is shown correctly in the winestore.database distribution file. AUTHOR: Same error again, and still correct. {120} Example 3-2; In the example 3-2 (page 120) you have: salary and birth_date transposed in their table positions. (143) 6th paragraph; "We've now completed the process of inserting rows into other tables in the winestore is similar" should be: "We've now completed the process of inserting rows into other tables in the winestore" (145) SQL under 3rd paragraph; there is no customer_id in table orders, the sql statement should read SELECT max(order_id) FROM orders WHERE cust_id = 1; (147) 4th paragraph the sentence "To test whether two items are equal, the != operator is provided"; Should be: "To test whether two items are not equal, the != operator is provided"; (164) 3rd paragraph (item 4.); "Use mysql_num_fields() is used to return..." should be: "mysql_num_fields() is used to return..." {167} IN PRINT: Last sentence on page; "...retrieves the tenth row of the result set." SHOULD BE: "...retrieves the eleventh row of the result set." {178} middle of the code example if (!($connection = @ mysql_connect($hostname, should be if (!($connection = @ mysql_connect($hostName, {178} about 2/3 down the page if (!(mysql_select_db("winestore", $connection))) should be if (!(mysql_select_db($databaseName, $connection))) {191} 1st paragraph Only one row should be produced per wine, not one per inventory. To do this, remove the inventory table attributes from the SELECT statement and add a DISTINCT to remove the duplicates. However, you can't remove the inventory table fully from the query, because you still need to ORDER BY date_added to display the newest wines added to our winestore cellar. The query is now as follows: $query = "SELECT wi.winery_name, w.year, w.wine_name, w.description, w.wine_id FROM wine w, winery wi, inventory i WHERE w.description != \"\" AND w.winery_id = wi.winery_id AND w.wine_id = i.wine_id GROUP BY winde-id ORDER BY i.date_added DESC LIMIT 3"; With this modified query, one entry is produced per wine. However, having removed the inventory attributes, you no longer have the pricing information. Should say - One approach is to remove the inventory table attributes from the SELECT statement and add a DISTINCT to remove the duplicates. Another approach is to remove the inventory attributes and add a GROUP BY wine_id. For example, the query would now be as follows: $query = "SELECT wi.winery_name, w.year, w.wine_name, w.description, w.wine_id FROM wine w, winery wi, inventory i WHERE w.description != \"\" AND w.winery_id = wi.winery_id AND w.wine_id = i.wine_id GROUP BY winde-id ORDER BY i.date_added DESC LIMIT 3"; However, these approaches don't work, because you can't remove the inventory table since you need the ORDER BY date_added to display the newest wines. Instead, we take a different approach. {191} First example query; The first query on the page contains the line: GROUP BY winde-id This should be: GROUP BY w.wine_id {192} middle; The query at the start of the function showPrices lacks quotes. (192) showPricing Query; $query = "SELECT min (cost), min (case_cost) - spaces between min and ( should be eliminated, otherwise this query will generate a SQL error. {192} 3/4 page; In the function showPrices, when the pricing information is printed the $row["min(cost)"] and $row["min(case_cost)"] are interchanged. (195) 4th paragraph; "... minor changes to THE configuration" should be "... minor changes to the configuration" [198] Example 4-13: Line 15 (not counting blank lines); The code as presented does not work because on line 15 the variable $query is set, but it is never used in later code. The variable which is later used is $result. So - $query should be: $result (209) Code under 1st paragraph; there is a space in the url example.5-4.php? regionName... This should be example.5-4.php?regionName... Similarly the next bit of code should not have a line break after the ? {216} bottom; The scipt of Example 5.5 does not include the file error.inc Therefor the function showerror() can not be called. (218) bottom of page, #2; "
in Example 5-1" should be " in Example 5-2" (224) 3rd paragraph; "queryING" should be "querying" {229} 1/2; After the use of the function mysql_connect() the function showerror() is used, this shoud be die("Could not connet"). {235} Example 5-11; The variable $numRowsToFetch in the 6th line of the code fragment on page 235 should be replaced with the constant ROWS. The book version displays the current page as a link, when it should be displayed as static text. The code has been updated on the author's web site at http://www.webdatabasebook.com/ {237} last sentence (bulleted); the function selectDistinct() does not take a $database parameter at all Correct: Delete the last bullet point on p.237. (246) Figure 6-2; Graphic says: "Script produces a receipt. Database modification does not occurs." - should say "does not occur" (266) 2nd paragraph, 4th sentence; This sentence reads as follows: There is only bottle left, and the user quickly adds this to her shopping cart. it should be:: There is only one bottle left, and the user quickly adds this to her shopping cart. (276) last line of page; "...does exactly same thing as..." should be "...does exactly the same thing as..." {287} last section of the Validating Email Addresses example The piece of code should be replaced with: // Extract the domain of the email address $maildomain = substr(strstr($formVars["email"], '@'), 1); elseif (!( getmxrr($maildomain, $temp) || gethostbyname($maildomain) != $maildomain )) // Can email be sent to this domain? $errors["email"] = "The domain does not exist."; The function getmxrr( ) queries an Internet domain name server (DNS) to check if there is a record of the email domain as a mail exchanger (MX). If the domain isn't an MX, the domain is checked with gethostbyname( ) to see if it has an A record; the relevant standard RFC-974 states that when a domain does not have an MX, it should be interpreted as having one equal to the host name. If both tests fail, the domain of the email address isn't valid and we reject the email address. {304} Last if statement on the page; The parentheses for the last 'if' expression are unbalanced: "if (($depBits[3] == "pm" && $arrBits[3] == "am")) ||" should be "if (($depBits[3] == "pm" && $arrBits[3] == "am") ||" {306} IN PRINT: Example 7-6, 3rd line from bottom, 3rd paramater to window.open(); "scrollbar=yes" SHOULD BE: "scrollbars=yes" {333} function fieldError(); the function should return the string, rather than echo the string, as it is echo'd when the function is called. The same goes for the full code listing on page 334 [353] last "if" statement of page; if !authenticateUser($connection, $PHP_AUTH_USER, $PHP_AUTH_PW))) SHOULD BE... if (!authenticateUser($connection, $PHP_AUTH_USER, $PHP_AUTH_PW)) missing first "(" and one too many ")" on end (354) Sentence after final code example; "Figure 10-4 shows the final results..." should be "Figure 10-3 shows the final results..." (366) example 9-11, about the fourth paragraph; // Set a boolean flag to true if this request // originated from the same IP address // as the one that created this session Should be: // Set a boolean flag to true if this request // did not originate from the same IP address // as the one that created this session [433] 3rd if statement; The if statement to redirect to the search saved page doesn't really exist. In the download, this has been fixed. [440] middle; The first two queries on this page are incorrect and will crash the application. They are missing the restriction "cust_id = -1". The first query should be: $query = "UPDATE orders SET cust_id = $custID , " . "order_id = " . $newOrder_no . " WHERE cust_id = -1 AND order_id = $order_no "; The second query should be: $query = "UPDATE items SET cust_id = $custID , " . "order_id = " . $newOrder_no . " WHERE cust_id = -1 AND order_id = $order_no"; (512) about 6th para (a one line para); "The final two relationships are a more difficult to identify and annotate." should be: "The final two relationships are more difficult to identify and annotate." (513) Figure C-4, top of page; The figure is a diagram of the entity-relationship model of a wine store database described in the book. Lines between entities and relationships are labeled to show the cardinalities of the relationship as described on page 512 (paragraphs 3-5). Several of the labels are inconsistent with said description. The following relationships are labeled incorrectly: * the between [wine] and [winery] should have a 1 instead of an M on the [winery] side. * between [winery] and [region] is unlabeled. Should have an M on the [winery] side and a 1 on the [region] side * between [item] and [order] is labeled with 2 Ms. the one on the [order] side should be a 1 * between [wine] and [inventory] is missing the 1 on the [wine] side (520) middele and bottom; The PRIMARY KEY for the orders and items tables includes "order_no", which is not a field in either table and is not defined. It should read "order_id" in both cases, which is a field and is thus defined. This is shown correctly in the winestore.database distribution file. AUTHOR: The reader is correct. (521) middle; The text says " . . . the identifier cust_id from customer is added to the users table and defined as the primary key attribute". However, in the CREATE TABLE text immediately following, the PRIMARY KEY attribute is "user_name", not "cust_id", though "cust_id" has been added as a field. The winestore.database distribution file also uses "user_name" as the PRIMARY KEY attribute. AUTHOR: Yes, it's a bug, but it has no effect. The cust_id and user_name are interchangeable as primary keys. Reason: The users table is a somewhat artifical creation to illustrate one-to-one relationships in ER modelling, and would in practice be part of the customer table. A second advantage of having two tables is that the user management module is separate and can be transplanted to another application. (522) top; The PRIMARY KEY for the items table includes "order_no", which is not a field in the table and is not defined. It should read "order_id", which is a field and is thus defined. This is shown correctly in the winestore.database distribution file. AUTHOR: The reader is correct. {529} Top code block; start_session(); should be session_start(); {534} top of page, ex d-6; example shows function definition as function sessionClose($sess_id) The function does not take a parameter and should be function sessionClose() AUTHOR: The reader is correct (though it doesn't actually cause anything bad to happen). I won't change it in the online source distribution, but I would fix it in a 2nd edition of the book.