Web design seattle - 146Part IPHP: The Basics$original = More than

146Part IPHP: The Basics$original = More than meets the eye ; $chopped = chop($original); $ltrimmed = ltrim($original); $trimmed = trim($original); print( The original is $original
); print( Its length is . strlen($original) .
); print( The chopped version is $chopped
); print( Its length is . strlen($chopped) .
); print( The ltrimmed version is $ltrimmed
); print( Its length is . strlen($ltrimmed) .
); print( The trimmed version is $ltrimmed
); print( Its length is . strlen($trimmed) .
); The result as viewed by a browser is: The original is More than meets the eye Its length is 28The chopped version is More than meets the eye Its length is 25The ltrimmed version is More than meets the eye Its length is 26The trimmed version is More than meets the eye Its length is 23The original string had three spaces at the end (subject to removal by chop()or trim()) and two at the beginning (removed by ltrim()and trim()). We were careful to describe ourresult as viewed by a browser because the multiple spaces have apparently been collapsed toone in the output, as browsers will do. If we viewed the HTML source produced by PHP origi- nally, we would still see sequences of two and three spaces. In addition to spaces, these functions remove whitespace like that denoted by the escapesequences n, r, t, and (end-of-line characters, tabs, and the null character used to terminate strings in C programs). You will hear the name chop()more frequently, but the identical function can also be calledwith the more logical name of rtrim(). Finally, notice that although chop()sounds extremelydestructive, it does not harm the $originalargument, which retains the same value. String replacementThe substring functions we ve seen so far are all about choosing a portion of the argumentrather than building a genuinely new string. Enter the functions str_replace()and substr_replace(). The str_replace()function enables you to replace all instances of a particular substringwith an alternate string. It takes three arguments: the string to be searched for, the string toreplace it with when it is found, and the string to perform the replacement on. For example: $first_edition = Burma is similar to Rhodesia in at least one way. ; $second_edition = str_replace( Rhodesia , Zimbabwe , $first_edition); $third_edition = str_replace( Burma , Myanmar , $second_edition); print($third_edition); #BREAK# 147Chapter 8Stringsgives us: Myanmar is similar to Zimbabwe in at least one way. This replacement will happen for all instances found of the search string. If our outdatedencyclopedia could be snarfed into a single PHP string, we could update it in one pass. One subtlety to be aware of: What happens when multiple instances of the search string over- lap? For example, with code like: $tricky_string = ABA is part of ABABA ; $maybe_tricked = str_replace( ABA , DEF , $tricky_string); print( Substitution result is $maybe_tricked
); the behavior we see is: Substitution result is DEF is part of DEFBA which is probably as reasonable as any other alternative. As we ve seen, str_replace()picks out portions to replace by matching to a target string; by contrast, substr_replace()chooses a portion to replace by its absolute position. Thefunction takes up to four arguments: the string to perform the replacement on, the string toreplace with, the starting position for the replacement, and (optionally) the length of the section to be replaced. For example: print(substr_replace( ABCDEFG , - , 2, 3)); gives us: AB-FGThe CDEportion of the string has been replaced with the single -. Notice that we are allowedto replace a substring with a string of a different length. If the length argument is omitted, it isassumed that you want to replace the entire portion of the string after the start position. The substr_replace()function also takes negative arguments for starting position andlength, which are treated exactly the same way as in the substr()function (described in theearlier section Substring selection ). It is important to remember with both str_replaceand substr_replacethat the original string remains unchanged by these operations. Finally, we have a couple more whimsical functions that produce new strings from old. Thestrrev()function simply returns a new string with the characters of its input in reverseorder. The str_repeat()function takes a string argument and an integer argument andreturns a string that is the appropriate number of copies of the string argument tackedtogether. For example: print(str_repeat( cheers , 3)); gives us: cheers cheers cheersfor the end of this section at long last. The substring search and replacement functions are summarized in Table 8-2.10 #BREAK# 148Part IPHP: The BasicsTable 8-2: Substring and String Replacement FunctionsFunctionBehaviorsubstr()Returns a subsequence of its initial string argument, as specified bythe second (position) argument and optional third (length) argument. The substring starts at the indicated position and continues for asmany characters as specified by the length argument or until the endof the string, if there is no length argument. A negative position argument means that the start character is locatedby counting backward from the end, whereas a negative lengthargument means that the end of the substring is found by countingback from the end, rather than forward from the start position. chop(), or rtrim()Returns its string argument with trailing (right-hand side) whitespaceremoved. Whitespace is , n, r, t, and . ltrim()Returns its string argument with leading (left-hand side) whitespaceremoved. Trim()Returns its string argument with both leading and trailing whitespaceremoved. Str_replace()Used to replace target substrings with another string. Takes three stringarguments: a substring to search for, a string to replace it with, and thecontaining string. Returns a copy of the containing string with allinstances of the first argument replaced by the second argument. Substr_replace()Puts a string argument in place of a position-specified substring. Takesup to four arguments: the string to operate on, the string to replacewith, the start position of the substring to replace, and the length ofthe string segment to be replaced. Returns a copy of the first argumentwith the replacement string put in place of the specified substring. If the length argument is omitted, the entire tail of the first stringargument is replaced. Negative position and length arguments aretreated as in substr(). Case functionsThese functions change lowercase to uppercase and vice versa. The first two (de)capitalizeentire strings, whereas the second two operate only on first letters of words. strtolower() The strtolower()function returns an all-lowercase string. It doesn t matter if the original isall uppercase or mixed. This fragment: returns the string they don t know they re shouting . #BREAK# 149Chapter 8StringsIf you have been faced with extensive form-validation needs before, you might already havenoticed that strtolower()is extremely handy for use with those that still think theire-mail addresses contain capital letters. Subsequent functions in this category will prove sim- ilarly useful. strtoupper() The strtoupper()function returns an all-uppercase string, regardless of whether the originalwas all lowercase or mixed: strtoupper($original) ); ?> ucfirst() The ucfirst()function capitalizes only the first letter of a string: ucwords() The ucwords()function capitalizes the first letter of each word in a string: Neither ucwords()nor ucfirst()converts anything into lowercase. Each makes only theappropriate leading letters into uppercase. If there are inappropriate capital letters in themiddle of words, they will not be corrected. Escaping functionsOne of the virtues of PHP is that it is willing to talk to almost anybody. In its role as a glue lan- guage, PHP talks to database servers, to LDAP servers, over sockets, and over the HTTP con- nection itself. Frequently, it accomplishes this communication by first constructing a messagestring (like a database query) and then shipping it off to the receiving program. Often, how- ever, the program attaches special meanings to certain characters, which therefore have tobe escaped,meaning that the receiving program is told to take them as a literal part of thestring rather than treating them specially. Many users deal with this issue by enabling magic-quotes, which ensures that quotes areescaped before strings are inserted into databases. If that s not feasible or desirable, thereare good old-fashioned strip-slashing and add-slashing by hand. The addslashes()functionescapes quotes, double quotes, backslashes, and NULLs with backslashes, because these arethe characters that typically need to be escaped for database queries. NoteTip10 #BREAK# 150Part IPHP: The Basics This will prevent the SQL statement from thinking it s finished right before the letter I. Whenyou pull the data back out, you ll need to use stripslashes()to get rid of the slashes. ; $qm_string = quotemeta($literal_string); echo $qm_string; will print: These characters ($, *) are very special to me\nFor escaping functions specific to HTML, see the Advanced String Functions section inChapter 22. Printing and outputThe workhorse constructs for printing and output are printand echo, which we cover indetail in Chapter 5. The standard way to print the value of variables to output is to includethem in a doubly quoted string (which will interpolate their values) and then give that stringto printor echo. If you need even more tightly formatted output, PHP also offers printf()and sprintf(), which are modeled on C functions of the same name. The two functions take identical argu- ments: a special format string (described later in this section) and then any number of otherarguments, which will be spliced into the right places in the format string to make the result. The only difference between printf()and sprintf()is that printf()sends the resultingstring directly to output, whereas sprintf()returns the result string as its value. To C programmers:This sprintf()function is slightly different from C s version in that youneed not supply an allocated string for sprintf()to write into PHP allocates the resultstring for you. The complicated bit about these functions is the format string. Every character that you putin the string will show up literally in the result, except the %character and characters thatimmediately follow it. The %character signals the beginning of a conversion specification, which indicates how to print one of the arguments that follow the format string. NoteCross- Reference10 #BREAK# 151Chapter 8StringsAfter the %, there are five elements that make up the conversion specification, some of whichare optional: padding, alignment, minimum width, precision, and type. .The single (optional) paddingcharacter is either a 0or a space (). This character isused to fill any space that would otherwise be unused but that you have insisted (withthe minimum width argument) be filled with something. If this padding character is notgiven, the default is to pad with spaces. .The optional alignmentcharacter (-) indicates whether the printed value should beleft- or right-justified. If present, the value will be left-justified; if absent, it will be right-justified. .An optional minimum widthnumber that indicates how many spaces this value shouldtake up, at a minimum. (If more spaces are needed to print the value, it will overflowbeyond its bounds.) .An optional precision specifier is written as a dot (.) followed by a number. It indicateshow many decimal points of precisiona double should print with. (This has no effect onprinting things other than doubles.) .A single character indicating how the typeof the value should be interpreted. The fcharacter indicates printing as a double, the scharacter indicates printing as a string, and then the rest of the possible characters (b, c, d, o, x, X) mean that the value shouldbe interpreted as an integer and printed in various formats. Those formats are bforbinary, cfor printing the character with the corresponding ASCII values, ofor octal, xfor hexadecimal (with lowercase letters) and X for hexadecimal with uppercase letters. Here s an example of printing the same double in several different ways:

  

gives us: 3.141590, 3.141590,3.141590000000000, 3.14The


construct is HTML that tells the browser to format the enclosed block liter- ally, without collapsing many spaces into one, and so on. Extended Example: An Exercise CalculatorIn this section, we continue the exercise calculator example from Chapter 5 by using a variety of string functions to process strings posted from a user form. In the previous exam- ple, we had just managed to pass off a string variable from an HTML form to the PHP pagedesigned to receive it. In this version, we actually do some analysis of the string we receive. (See the end of this section for reasons why this example will still need to be improved inlater chapters.) Listing 8-1 shows the HTML form used to prompt the user for an exercise to analyze. This is largely the same as the corresponding form in Chapter 7, with a different form-handling target. #BREAK# 152Part IPHP: The BasicsListing 8-1:The entry form

Workout calculator (passing a string)

Enter an exercise, and we ll tell you how long you d have todo it
to burn one pound of fat.



Listing 8-2 shows a revised form-handling page that displays different exercise stats depend- ing on the particular exercise entered by the user. We very intentionally used the modern $_POSTsuperglobal to catch the value of the stringposted by the form. This means, however, that the code in Listing 8-2 will not run in any ver- sion of PHP earlier than PHP 4.1. To adapt it to an earlier version, replace $_POSTwith $HTTP_POST_VARS with the understanding that the long variable names are now officially deprecated and will probably result in broken code at some point. Listing 8-2:Form handler using string functions 50) { echo You aren t playing by the rules. Bad dog! ; exit; Caution10 #BREAK# 153Chapter 8Strings} // Try to parse the input string// —————————– // Make sure there aren t any spaces before or after$exercise = trim($exercise); // Convert to all lowercase for better string matching$exercise = strtolower($exercise); // Try to standardize on gerund form, if possibleif (strpos($exercise, ing ) > 0) { // Already good$exercise_str = $exercise; } elseif ($exercise == bike || $exercise == cycle ) { $exercise_str = cycling ; } elseif ($exercise == run || $exercise == jog ) { $exercise_str = running ; } elseif ($exercise == soccer || $exercise == football ) { $exercise_str = soccer ; } elseif (strstr($exercise, weight ) || strstr($exercise, strength )) { $exercise_str = weight lifting ; } // Now assign a number of hours to burn one pound of fat to each// sportif ($exercise_str == cycling || $exercise_str == biking ) { $hours = 5 hours and 40 minutes ; } elseif ($exercise_str == running || $exercise_str == jogging ) { $hours = 4 hours and 30 minutes ; } elseif ($exercise_str == soccer || $exercise_str == football ) { $hours = 4 hours and 30 minutes ; } elseif ($exercise_str == weight lifting ) { $hours = 7 hours and 30 minutes ; } else { // Nullify all other exercises$exercise_str = ; $hours = ; } // Construct a sentence// ——————– if ($exercise_str != && $hours != ) { $message = It would take .$hours. of . $exercise_str . to burn one pound of fat. ; } elseif ($exercise_str == && $hours == ) { // If the exercise isn t in the list above, give a // default message. Continued10 #BREAK# 154Part IPHP: The BasicsListing 8-2(continued) $message = Sorry, we do not have data for that exercise. ; } else { // There are two other logical possibilities // 1. We recognize the exercise but don t have a duration // for it // 2. We don t recognize the exercise but somehow have a // duration // neither should happen, but just in case… $message = Something has gone horribly wrong! ; } // Now lay out the page// ——————– $page_str = <<< EOPAGE

Workout calculator handler, part 2

The workout calculator says, $message

EOPAGE; echo $page_str; ?> In order, the code in Listing 8-2: 1.Receives the posted string from the HTML form. 2.Tries to put the received string into a standard form by trimming off whitespace, converting to lowercase, and translating some known variations. #BREAK# 155Chapter 8Strings3.Uses the cleaned-up and translated string to look up data on a given exercise. 4.Uses the heredoc syntax to construct a response page. Figures 8-1 and 8-2 show what is displayed as the user enters the word bike. Figure 8-1:The entry formFigure 8-2:The answerSo, do we like our exercise calculator yet? The main problem is that it is highly likely that the form will not know how to handle what the user types (other than by printing a genericmessage). Also, if you raise error reporting up to E_ALL, you will see some warnings due touninitialized variables. The user is also not given any clue as to what kinds of input the formwill be able to handle. #BREAK# 156Part IPHP: The BasicsIn general, unless you are building a system (like a search engine) where the whole point isdealing with free text, it is a bad idea to have the results of your code depend on analysis ofuser-entered strings. If you want the user to make choices, you should constrain those choicesso that you know what is coming. User-entered text is fine as long as its ultimate fate is to beviewed by another human being (after, say, being stored in a database or forwarded by ane-mail program). To gracefully constrain the user s choices, you need HTML user-interface elements other thanthe text box, such as check boxes, radio buttons, and pull-down lists. And to make use ofthose UI elements on the receiving side, we really need to exploit the power of arrays morefully. We will extend this example in Chapter 9 by doing just that. SummaryStringsare sequences of characters, and the string is one of the eight basic datatypes in PHP. Unlike in some other languages, there is no distinct character type, since single charactersbehave as strings of length 1. Literal strings are specified in code by either single ( ) or dou- ble ( ) quotes. Singly quoted strings are interpreted nearly literally, while doubly quotedstrings interpret a number of escape sequences and automatically interpolate variable values. The main string operator is . , which concatenates two strings together. In addition, thereis a dizzying array of string functions, which help you inspect, compare, search, extract, chop, replace, slice, and dice strings to your heart s content. For the most sophisticatedstring-manipulation needs, PHP supports both POSIX and Perl-compatible regular expres- sions (covered in Chapter 22). … #BREAK# Arrays and ArrayFunctionsArrays are definitely one of the coolest and most flexible features ofPHP. Unlike vector arrays from other languages (C, C++, Pascal), PHP arrays can store data of varied types and automatically organize itfor you in a large variety of ways. This chapter treats arrays and array functions in some depth. For avery quick introduction to the syntax and use of arrays, see Chapter5. For a more complete survey of advanced array functions, seeChapter 21. The Uses of ArraysAn array is a collection of variables indexed and bundled togetherinto a single, easily referenced super-variable that offers an easy wayto pass multiple values between lines of code, functions, and evenpages. Throughout much of this chapter, we will be looking at theinner workings of arrays and exploring all the built-in PHP functionsthat manipulate them. Before we get too deep into that, however, it sworth listing the common ways that arrays are used in real PHP code. Many built-in PHP environment variables are in the form of arrays(for example, $_SESSION, which contains all the variable names andvalues being propagated from page to page via PHP s session mecha- nism). If you want access to them, you need to understand, at a mini- mum, how to reference arrays. Most database functions transport their info via arrays, making acompact package of an arbitrary chunk of data. It s easy to pass entire sets of HTML form arguments from one pageto another in a single array (see Chapter 7). Arrays make a nice container for doing manipulations (sorting, count- ing, and so on) of any data you develop while executing a singlepage s script. Almost any situation that calls for a number of pieces of data to bepackaged and handled as one is appropriate for a PHP array. Cross- Reference99CHAPTER …In This ChapterAn all-purpose datatypeStoring and retrievingvaluesMultidimensional arraysIteration …#BREAK# 158Part IPHP: The BasicsWhat Are PHP Arrays? PHP arrays are associativearrays with a little extra machinery thrown in. The associativepartmeans that arrays store element values in association with key values rather than in a strictlinear index order. (If you have seen arrays in other programming languages, they are likely to have been vectorarrays rather than associative arrays see the related sidebar for anexplanation of the difference.) If you store an element in an array, in association with a key, all you need to retrieve it later from that array is the key value. For example, storage is assimple as this: $state_location[ San Mateo ] = California ; which stores the element California in the array variable $state_location, in associa- tion with the lookup key San Mateo . After this has been stored, you can look up the storedvalue by using the key, like so: $state = $state_location[ San Mateo ]; // equals California Simple, no? If all you want arrays for is to store key/value pairs, the preceding information is all you needto know. Similarly, if you want to associate a numerical ordering with a bunch of values, allyou have to do is use integers as your key values, as in: $my_array[1] = The first thing ; $my_array[2] = The second thing ; // and so on … For Perl programmers:Arrays in PHP are much like hashes in Perl, with some syntactic differences. For one thing, all variables in PHP are denoted with a leading $, not just scalarvariables. Second, even though the array is associative, the indices are grouped by squarebrackets ([]) rather than curly braces ({}). Finally, there is no array or list type indexed onlyby integers. The convention is to use integers as associative indices, and the array itself main- tains an internal ordering for iteration purposes. In addition to the machinery that makes this kind of key/value association possible, arraystrack some other things behind the scenes. Because of this, we sometimes treat them asother kinds of data structures. As we will see, arrays can be multidimensional. They can storevalues in association with a sequence of key values rather than a single key. Also, arrays automatically maintain an ordered list of the elements that have been inserted in them, independent of what the key values happen to be. This makes it possible to treat arrays aslinked lists. In general, we will reveal the workings of this extra machinery as we explore thefunctions that use it. A note for C++ programmers:You should be aware that arrays can handle some of the sametasks that require the use of template libraries in C++. Much of the reason for having tem- plates in the first place is to get around restrictions having to do with strict typing of data. PHP s looser typing system makes it possible, for example, to write general algorithms thatiterate over the contents of arrays without committing to the type of the array elementsthemselves. NoteNote11 #BREAK# 159Chapter 9Arrays and Array FunctionsA general note for programmers familiar with other languages:PHP does not need verymany different kinds of data structures, in part because of the great flexibility offered by PHParrays. By careful choice of a subset of array functions, you can make arrays pretend to actlike vector arrays, structure/record types, linked lists, hash tables, or stacks and queues data structures that in other languages either require their own data types or more abstruselanguage features such as pointers and explicit memory management. NoteAssociative arrays versus vector arraysIf you have programmed in languages like C, C++, and Pascal, you are probably used to a particu- lar usage of the word array, one that doesn t match the PHP usage very well at all. A more specificterm for a C-style array is a vector array, whereas a PHP-style array is an associative array. In a vector array, the contained elements all need to be of the same type, and usually the lan- guage compiler needs to know in advance how many such elements there are likely to be. Forexample, In C you might declare an array of 100 double-precision floating-point numbers with astatement like: double my_array[100]; // This is C, not PHP! The restriction on types and the advance declaration of size have an associated benefit: Vectorarrays are very fast, both for storage and lookup. The reason is that the compiler will usually layout the array in a contiguous block of computer memory, as large as the size of the element typemultiplied by the number of elements. This makes it very easy for the programming language tolocate a particular array slot all it needs to know is the starting memory address of the array, thesize of the element type, and the index of the element it wants to look up, and it can directlycompute the memory address of that slot. By contrast, PHP arrays are associative (and so some would call them hashes,rather than arrays). Rather than having a fixed number of slots, PHP creates array slots as new elements are addedto the array. Rather than requiring elements to be of the same type, PHP arrays have the sametype-looseness that PHP variables have you can assign arbitrary PHP values to be array ele- ments. Finally, because vector arrays are all about laying out their elements in numerical order; the keys used for lookup and storage must be integer numbers. PHP arrays can have keys of arbi- trary type, instead, including string keys. So, you could have successive array assignments like: $my_array[1] = 1; $my_array[ orange ] = 2; $my_array[3] = 3; without any paradox. The result is that your array has three values (1, 2, 3), each of which isstored in association with a key (1, orange , and 3, respectively). The extra flexibility of associative arrays comes at a price, because there is a little bit more goingon between your code and the actual computation of a memory address than is true with vectorarrays. For most Web programming purposes, however, this extra access time is not a significantcost. The fact that integers are legal keys for PHP arrays means that you can easily imitate the behav- ior of a vector array, simply by restricting your code to use only integers as keys. #BREAK# 160Part IPHP: The BasicsCreating ArraysThere are three main ways to create an array in a PHP script: by assigning a value into one(and thereby implicitly creating it), by using the array()construct, and by calling a functionthat happens to return an array as its value. Direct assignmentThe simplest way to create an array is to act as though a variable is already an array andassign a value into it, like this: $my_array[1] = The first thing in my array that I just made ; If $my_arraywas an unbound variable (or bound to a nonarray variable) before this state- ment, it will now be a variable bound to an array with one element. If instead $my_arraywasalready an array, the string will be stored in association with the integer key 1. If no value wasassociated with that number before, a new array slot will be created to hold it; if a value wasassociated with 1, the previous value will be overwritten. (You can also assign into an arrayby omitting the index entirely as in $my_array[], described later in this chapter.) The array() constructThe other way to create an array is via the array()construct, which creates a new arrayfrom the specification of its elements and associated keys. In its simplest version, array() iscalled with no arguments, which creates a new empty array. In its next simplest version, array()takes a comma-separated list of elements to be stored, without any specification ofkeys. The result is that the elements are stored in the array in the order specified and areassigned integer keys beginning with zero. For example, the statement: $fruit_basket = array( apple , orange , banana , pear ); causes the variable $fruit_basketto be assigned to an array with four string elements( apple , banana , orange , pear ), with the indices 0, 1, 2, and 3, respectively. In addition (as we ll see in the Iteration section later in this chapter), the array will rememberthe order in which the elements were stored. The assignment to $fruit_basket, then, has exactly the same effect as the following: $fruit_basket[0] = apple ; $fruit_basket[1] = orange ; $fruit_basket[2] = banana ; $fruit_basket[3] = pear ; assuming that the $fruit_basketvariable was unbound at the first assignment. The sameeffect could also have been accomplished by omitting the indices in the assignment, like so: $fruit_basket[] = apple ; $fruit_basket[] = orange ; $fruit_basket[] = banana ; $fruit_basket[] = pear ; In this case, PHP again assumes that you are adding sequential elements that should havenumerical indices counting upward from zero. #BREAK# 161Chapter 9Arrays and Array FunctionsYes, the default numbering for array indices starts at zero, not one. This is the convention forarrays in most programming languages. We re not sure why computer scientists start count- ing at zero (mathematicians, like everyone else in the world, start with one), but it probablyhas its origin in the kind of pointer arithmetic that calculates memory addresses for vectorarrays. Addresses for successive elements of such arrays are found by adding successivelylarger offsets to the array s address, but the offset for the first element is zero (because thefirst element s address is the same as the array s address). Specifying indices using array() The simple example of array()in the preceding section assigns indices to our elements, butthose indices will be the integers, counting upward from zero we re not getting a lot ofchoice in the matter. As it turns out, array()offers us a special syntax for specifying whatthe indices should be. Instead of element values separated by commas, you supply key-valuepairs separated by commas, where the key and value are separated by the special symbol =>. Consider the following statement: $fruit_basket = array(0 => apple , 1 => orange , 2 => banana , 3 => pear ); Evaluating it will have exactly the same effect as our earlier version each string will bestored in the array in succession, with the indices 0, 1, 2, 3in order. Instead, however, we canuse exactly the same syntax to store these elements with different indices: $fruit_basket = array( red => apple , orange => orange , yellow => banana , green => pear ); This gives us the same four elements, added to our new array in the same order, but indexedby color names rather than numbers. To recover the name of the yellow fruit, for example, wejust evaluate the expression: $fruit_basket[ yellow ] // will be equal to banana Finally, as we said earlier, you can create an empty array by calling the arrayfunction withno arguments. For example: $my_empty_array = array(); creates an array with no elements. This can be handy for passing to a function that expectsan array as argument. Functions returning arraysThe final way to create an array in a script is to call a function that returns an array. This maybe a user-defined function, or it may be a built-in function that makes an array via methodsinternal to PHP. Many database-interaction functions, for example, return their results in arrays that the func- tions create on the fly. Other functions exist simply to create arrays that are handy to have asgrist for later array-manipulating functions. One such is range(), which takes two integers asarguments and returns an array filled with all the integers (inclusive) between the arguments. In other words: $my_array = range(1,5); is equivalent to: $my_array = array(1, 2, 3, 4, 5); Note11 #BREAK# 162Part IPHP: The BasicsRetrieving ValuesAfter we have stored some values in an array, how do we get them out again? Retrieving by indexThe most direct way to retrieve a value is to use its index. If we have stored a value in$my_arrayat index 5, $my_array[5]should evaluate to the stored value. If $my_arrayhas never been assigned, or if nothing has been stored in it with an index of 5, $my_array[5] will behave like an unbound variable. The list() constructThere are a number of other ways to recover values from arrays without using keys, most ofwhich exploit the fact that arrays are silently recording the order in which elements are stored. We cover this in more detail in this chapter s Iteration section, but one such example islist(), which is used to assign several array elements to variables in succession. Supposethe following two statements are executed: $fruit_basket = array( apple , orange , banana ); list($red_fruit, $orange_fruit) = $fruit_basket; This will assign the string apple to the variable $red_fruitand the string orange tothe variable $orange_fruit(with no assignment of banana , because we didn t supplyenough variables). The variables in list()will be assigned to elements of the array in theorder they were originally stored in the array. Notice the unusual behavior here the list() construct is on the left-hand side of the assignment operator (=), where we normally find onlyvariables. In some sense, list()is the opposite or inverse of array()because array()packages itsarguments into an array, and list()takes the array apart again into individual variableassignments. If we evaluate: list($first, $second) = array($first, second); the original values of $firstand $secondwill be assigned to those variables again, afterhaving been briefly stored in an array. We have been careful to refer to both array()and list()as constructs, rather than func- tions. This is because they are not in fact functions like certain other specialized PHP lan- guage features (if, while, function, and so on) they are interpreted specially by thelanguage itself and are not run through the usual routine of function-call interpretation. Remember that the arguments to a function call are evaluated before the function is reallyinvoked on those arguments, so constructs that need to do other kinds of interpretation onwhat they are given cannot be implemented as function calls. It s a useful exercise to lookhard at the example uses of both array()and list()to figure out why treating them asfunction calls could not result in the behavior advertised. Note11 #BREAK# 163Chapter 9Arrays and Array FunctionsMultidimensional ArraysSo far, the array examples we have looked at have all been one-dimensional, with only onelevel of bracketed keys. However, PHP can easily support multiple-dimensional arrays, witharbitrary numbers of keys. And just as with one-dimensional arrays, there is no need todeclare our intentions in advance the first reference to an array variable can be an assign- ment like: $multi_array[1][2][3][4][5] = deeply buried treasure ; That is a five-dimensional array with successive keys that happen, in this case, to be five successive integers. Actually, in our opinion, thinking of arrays as multidimensional makes matters more confusingthan they need to be. Instead, just remember that the values that are stored in arrays can them- selves be arrays, just as legitimately as they can be strings or numbers. The multiple-index syn- tax in the preceding example is simply a concise way to refer to a (four-dimensional) array thatis stored with a key of 1in $multi_array, which in turn has a (three-dimensional) array storedin it, and so on. Note also that you can have different depths of reference in different parts ofthe array, like so: $multi_level_array[0] = a simple string ; $multi_level_array[1][ contains ] = a string stored deeper ; The integer key of 0stores a string, and the key of 1stores an array that, in turn, has a stringin it. However, you cannot continue on with this assignment: $multi_level_array[0][ contains ] = another deep string ; without the result of losing the first assignment to asimplestring . The key of 0can beused to store a string or another array, but not both at once. If we remember that multidimensional arrays are simply arrays that have other arrays storedin them, it s easier to see how the array()creation construct generalizes. In fact, even thisseemingly complicated assignment is not that complicated: $cornucopia = array( fruit => array( red => apple , orange => orange , yellow => banana , green => pear ), flower => array( red => rose , yellow => sunflower , purple => iris )); It is simply an array with two values stored in association with keys. Each of these values isan array itself. After we have made the array, we can reference it like this: $kind_wanted = flower ; $color_wanted = purple ; print( The $color_wanted $kind_wanted is . $cornucopia[$kind_wanted][$color_wanted]); See the browser output: The purple flower is iris11 #BREAK# 164Part IPHP: The BasicsThere s a reason that we used the string concatenation operator .in the preceding printstatement, rather than simply embedding the $cornucopia[$kind_wanted][$color_ wanted]in our print string as we do with other variables. PHP3 string parsing can be con- fused by multiple array indices within a double-quoted string, so it needs to be concatenatedseparately. PHP since version 4 handles this in a better way you are safe embedding arrayreferences in a string as long as you enclose the reference in curly braces, like this: print( The thing we want is {$cornucopia[$kind_wanted][$color_wanted]} ); Finally, notice that there is no great penalty for misindexing into a multidimensional arraywhen we are trying to retrieve something; if no such key is found, the expression is treatedlike an unbound variable. So, if we try the following instead: $kind_wanted = fruit ; $color_wanted = purple ; //uh-oh, we didn t store any plumsprint( The $color_wanted $kind_wanted is . $cornucopia[$kind_wanted][$color_wanted]); The worst that happens is the unsatisfying: The purple fruit isThis is the worst thing that happens, of course, unless you have raised your error_reportinglevel to E_ALL, as we advise you to do at some points in this book. In that case, you will get awarning message about an undefined index ( purple ) just as you would if you had anunbound variable. Inspecting ArraysNow we can make arrays, store values in arrays, and then pull the values out again when wewant them. Table 9-1 summarizes a few other functions we can use to ask questions of ourarrays. Table 9-1: Simple Functions for Inspecting ArraysFunctionBehavioris_array()Takes a single argument of any type and returns a true value if theargument is an array, and false otherwise. count()Takes an array as argument and returns the number of nonemptyelements in the array. (This will be 1for strings and numbers.) sizeof()Identical to count(). in_array()Takes two arguments: an element (that might be a value in anarray), and an array (that might contain the element). Returns trueif the element is contained as a value in the array, falseotherwise. (Note that this does not test for the presence of keys in the array.) IsSet($array[$key])Takes an array[key] form and returns trueif the key portion is avalid key for the array. (This is a specific use of the more generalfunction IsSet(), which tests whether a variable is bound.) Note11 #BREAK# 165Chapter 9Arrays and Array FunctionsNote that all of these functions work on only the depth of the array specified, so that testingfor values layers deep in a multidimensional array requires that you specify out that numberof places. In the case of our preceding $cornucopiaexample, for instance: count($cornucopia) // what do you expect here? 2? 7? 9? returns a 2, whilecount($cornucopia[fruit] returns 4. Deleting from ArraysDeleting an element from an array is simple, exactly analogous to getting rid of an assignedvariable. Just call unset(), as in the following: $my_array[0] = wanted ; $my_array[1] = unwanted ; $my_array[2] = wanted again ; unset($my_array[1]); Assuming that $my_arraywas unbound when we started, at the end it has two values( wanted , wantedagain ), in association with two keys (0and 2, respectively). It is asthough we had skipped the original unwanted assignment (except that the keys are num- bered differently). Note that this is notthe same as setting the contents to an empty value. If, instead of callingunset(), we had the following statement: $my_array[1] = ; at the end we would have three stored values ( wanted , , wantedagain ) in associationwith three keys (0, 1, and 2, respectively). IterationWe ve seen how to put things into arrays, how to find them once we have put them there, andhow to delete them when we don t want them anymore. What we need next is a technique fordealing with array elements in bulk. Iteration constructs help us do this by letting us step orloop through arrays, element by element or key by key. We ll first delve briefly into the internal representation of arrays to understand how PHP sup- ports iteration. (Although important, this subsection is skippable if you want to use it butdon t want to know how it works, you can jump down to the section titled Using iterationfunctions. ) Support for iterationIn addition to storing values in association with their keys, PHP arrays silently build an orderedlist of the key/value pairs that are stored, in the order that they are stored. The reason for thisis to support operations that iterate over the entire contents of an array. (Notice that this is dif- ficult to do simply by building a loop that increments an index, because array indices are notnecessarily numerical.) #BREAK# 166Part IPHP: The BasicsThere is, in fact, sort of a hidden pointer system built into arrays. Each stored key/value pairpoints to the next one, and one side effect of adding the first element to an array is that a cur- rent pointer points to the very first element, where it will stay unless disturbed by one of theiteration functions. Each array remembers a particular stored key/value pair as being the current one, and arrayiteration functions work in part by shifting that current marker through the internal list ofkeys and values. Although we will call this marker the current pointer, PHP does not supportfull pointers in the sense that C and C++ programmers may be used to, and this usage of theword will turn up only in the context of iterating through arrays. This linked-list pointer system is an alternative way to inspect and manipulate arrays, whichexists alongside the system that allows key-based lookup and storage. Figure 9-1 shows anabstract view (not necessarily reflecting the real implementation) of how these systemslocate elements in an array. Figure 9-1:Internal structure of an arrayHashinglookupLinked liststructurecurrentIndexValueIndexValueIndexValueIndexValueIterationfunctionsIndex-basedfunctionsNote11 #BREAK# 167Chapter 9Arrays and Array FunctionsUsing iteration functionsTo explore the iteration functions, let s construct a sample array that we can iterate over. $major_city_info = array(); $major_city_info[0] = Caracas ; $major_city_info[ Caracas ] = Venezuela ; $major_city_info[1] = Paris ; $major_city_info[ Paris ] = France ; $major_city_info[2] = Tokyo ; $major_city_info[ Tokyo ] = Japan ; In this example, we created an array and stored some names of cities in it, in association withnumerical indices. We also stored the names of the relevant countries into the array, indexedby the city names. (We could have accomplished all this with one big call to array(), but theseparate statements make the structure of the array somewhat clearer.) Now, we can use the array key system to pull out the data we have stored. If we want to relyon the convention in the preceding example (cities stored with numerical indices, countriesstored with city-name indices), we can write a function that prints the city and the associatedcountry, like so: function city_by_number ($number_index, $city_array) { if (IsSet($city_array[$number_index])) { $the_city = $city_array[$number_index]; $the_country = $city_array[$the_city]; print( $the_city is in $the_country
); } } city_by_number(0, $major_city_info); city_by_number(1, $major_city_info); city_by_number(2, $major_city_info); If we have set $major_city, as in the previous block of code, the browser output we shouldexpect is: Caracas is in VenezuelaParis is in FranceTokyo is in JapanNow, this method of retrieval is fine when we know how the array is structured and we knowwhat all the keys are. But what if you would simply like to print everything that an array contains? Our favorite iteration method: foreachOur favorite construct for looping through an array is foreach. Although it is probably inher- ited from Perl s foreach, it has a somewhat odd syntax (which is not the same as Perl s oddsyntax). It comes in two flavors which one you decide to use will depend on whether youcare about the array s keys or just the values. #BREAK# 168Part IPHP: The Basicsforeach ($array_variable as $value_variable) { // .. do something with the value in $value_variable} // Note that this is an example template, not real PHP codeforeach ($array_variable as $key_var => $value_var) { // .. do something with $key_var and/or $value_var} Although in the preceding pseudocode we assume that the array of interest is in the variable$array_variable, you can have any expression that evaluates to an array in that position, for example: foreach (function_returning_array() as $value_variable) { // .. do something with the value in $value_variable} Like array()and list(), but unlike the genuine iteration functions in the rest of this sec- tion, foreachis a language construct, not a function. (See the earlier note about list() for an explanation of the difference.) As an example, let s write a function to print all the names from our sample array: function print_all_foreach ($city_array) { foreach ($city_array as $name_value) { print( $name_value
); } } print_all_foreach($major_city_info); print_all_foreach($major_city_info);// again, as an experimentAs output, we get all the names, in the order we stored them, twice over: CaracasVenezuelaParisFranceTokyoJapanCaracasVenezuelaParisFranceTokyoJapanWe printed the contents twice to show that calling the function is repeatable. Iterating with current() and next() We like foreach, but it is really only good for situations where you want to simply loopthrough an array s values. For more control, let s look at current()and next(). The current()function returns the stored value that the current pointer points to. (Referback to Figure 9-1 for a diagram of the array internals.) When an array is newly created withNote11 #BREAK# 169Chapter 9Arrays and Array Functionselements, the element pointed to will always be the first element. The next()function firstadvances that pointer and then returns the current value pointed to. If the next()functioniscalled when the current pointer is already pointing to the last stored value and, therefore, runs off the end of the array, the function returns a false value. As an example, we can print out an array s contents with the iteration functions current() and next(). (Notice that the final function call is repeated.) function print_all_next($city_array) { // warning–doesn t quite work. See the function each() $current_item = current($city_array); if ($current_item) print( $current_item
); elseprint( There s nothing to print ); while($current_item = next($city_array)) print( $current_item
); } print_all_next($major_city_info); print_all_next($major_city_info);// again, to see what happensThere is a gotcha lurking in the preceding code example, which doesn t bite us in this partic- ular example but makes this function untrustworthy as a general method for finding every- thing in an array. The problem is that we may have stored a false value in the array, which ourwhileloop won t be able to distinguish from the false value that next()returns when ithas run out of array elements. See the discussion of the each()function later in this chap- ter under Empty values and the each()function for a solution. When we execute this array-printing code, we get the following again: CaracasVenezuelaParisFranceTokyoJapanCaracasVenezuelaParisFranceTokyoJapanNow, how is it that we are seeing the same thing from the second call to print_all_next()? How did the current pointer get back to the beginning to start all over again the second time? The answer lies in the fact that PHP function calls are call-by-value, meaning that they copytheir arguments rather than operating directly on them. Both of the function calls, then, aregetting a fresh copy of their array argument, which has never itself been disturbed by a call to next(). For more on under what circumstances functions copy their arguments rather than operatingon them directly, see Chapter 6. Cross- ReferenceCaution11 #BREAK# 170Part IPHP: The BasicsWe can test this explanation by passing the arrays by reference rather than by value. If wedefine the same function but call it with ampersands (&) like this: print_all_next(&$major_city_info); print_all_next(&$major_city_info);// againWe get the following printing behavior: CaracasVenezuelaParisFranceTokyoJapanThere s nothing to printThe trick we used to test the array behavior (passing a variable reference to a function) hasbeen deprecated, so you may get a warning when running this code, in addition to seeingthe results printed above. The reason is that this time the current pointer of the global version of the array was movedby the first function call. Most of the iteration functions have both a returned value and a side effect. In the case of thefunctions next(), prev(), reset(), and end(), the side effect is to change the positionof the internal pointer, and what is returned is the value from the key/value pair pointed toafterthe pointer s position is changed. Starting over with reset() In the preceding section, we wrote a function intended to print out all the values in an array, and we saw how it could fail if the array s internal pointer did not start off at the beginning ofthe list of key/value pairs. The reset()function gives us a way to rewind that pointer tothe beginning it sets the pointer to the first key/value pair and then returns the storedvalue. We can use it to make our printing function more robust by replacing the call to current()with a call to reset(). function print_all_array_reset($city_array) { // warning–still not reliable. See the function each() $current_item = reset($city_array); //rewind, return valueif ($current_item) print( $current_item
); elseprint( There s nothing to print ); while($current_item = next($city_array)) print( $current_item
); } This function is somewhat more predictable in that it will always start with the first element, regardless of the pointer s location in the array it is handed. (Whether this is a good ideadepends, of course, on what the function is used for and whether its arguments are passed by value or by reference.) NoteNote11 #BREAK# 171Chapter 9Arrays and Array FunctionsPerhaps confusingly, we use our call to reset()in the preceding example both for its sideeffect (rewinding the pointer) and for its return value (the first value stored). Alternatively, we could replace the first real line of the function body with these two lines: reset($city_array); // rewind to the first element$current_item = current($city_array); // the first valueReverse order with end() and prev() We have seen the functions next(), which moves the current pointer ahead by one, andreset(), which rewinds the pointer to the beginning. Analogously, there are also the func- tions prev(), which moves the pointer back by one, and end(), which jumps the pointer to the last entry in the list. We can use these, for example, to print our array entries inreverse order. function print_all_array_backwards($city_array) { // warning–still not reliable. See the function each() $current_item = end($city_array); //fast-forward to lastif ($current_item) print( $current_item
); elseprint( There s nothing to print ); while($current_item = prev($city_array)) print( $current_item
); } print_all_array_backwards($major_city_info); If we call this on the same $major_city_infodata as in previous examples, we get the sameprintout in reverse order: JapanTokyoFranceParisVenezuelaCaracasExtracting keys with key() So far, we have printed only the values stored in arrays, even though we are storing keys aswell. The keys are also retrievable from the internal linked list of an array by using the key() function this acts just like current()except that it returns the key of a key/value pair, rather than the value. (Refer to Figure 9-1.) Using the key()function, we can modify one ofour earlier printing functions to print keys as well as values. function print_keys_and_values($city_array) { // warning–See the discussion of each() belowreset($city_array); $current_value = current($city_array); $current_key = key($city_array); if ($current_value) print( Key: $current_key; Value: $current_value
); #BREAK# 172Part IPHP: The Basicselseprint( There s nothing to print ); while($current_value = next($city_array)) { $current_key = key($city_array); print( Key: $current_key; Value: $current_value
); } } print_keys_and_values($major_city_info); With the same data as before, this gives us the browser output: Key: 0; Value: CaracasKey: Caracas; Value: VenezuelaKey: 1; Value: ParisKey: Paris; Value: FranceKey: 2; Value: TokyoKey: Tokyo; Value: JapanEmpty values and the each() functionWe have written several functions that print the contents of arrays by iterating through themand, as we have pointed out, all but the foreachversion have the same weakness. Each oneof them tests for completion by seeing whether next()returns a false value. This will reliablyhappen when the array runs out of values, but it will also happen if and when we encounter afalse value that we have actually stored. False values include the empty string ( ), the num- ber 0, and the Boolean value FALSE, any or all of which we might reasonably store as a datavalue for some task or other. To the rescue comes each(), which is somewhat similar to next()but has the virtue ofreturning false only after it has run out of array to traverse. Oddly enough, if it has not runout, each()returns an array itself, which holds both keys and values for the key/value pair it is pointing at. This characteristic makes each()confusing to talk about because you needto keep two arrays straight: the array that you are traversing and the array that each() returns every time that it is called. The array that each()returnshas the following fourkey/value pairs: .Key: 0; Value: current-key .Key: 1; Value: current-value .Key: key ; Value: current-key .Key: value ; Value: current-valueThe current-keyand current-valueare the key and value from the array being traversed. Inother words, the returned array packages up the current key/value pair from the traversedarray and offers both numerical and string indices to specify whether you are interested inthe key or the value. In addition to having a different type of return value, each()differs from next()in thateach()returns the value that was pointed to beforemoving the current pointer ahead, whereas next()returns the value afterthe pointer is moved. This means if you start with acurrent pointer pointing to the first element of an array, successive calls to each()will covereach array cell, whereas successive calls to next()will skip the first value. Note11 #BREAK# 173Chapter 9Arrays and Array FunctionsWe can use each()to write a more robust version of a function to print all keys and values inan array: function print_keys_and_values_each($city_array) { // reliably prints everything in arrayreset($city_array); while ($array_cell = each($city_array)) { $current_value = $array_cell[ value ]; $current_key = $array_cell[ key ]; print( Key: $current_key; Value: $current_value
); } } print_keys_and_values_each($major_city_info); Applying this function to our standard sample array gives the following browser output: Key: 0; Value: CaracasKey: Caracas; Value: VenezuelaKey: 1; Value: ParisKey: Paris; Value: FranceKey: 2; Value: TokyoKey: Tokyo; Value: JapanThis is exactly the same as was produced by our earlier function print_keys_and_values(). The difference is that our new function will not stop prematurely if one of the values is false orempty. Walking with array_walk() Our last iteration function lets you pass an arbitrary function of your own design over anarray, doing whatever your function pleases with each key/value pair. The array_walk() function takes two arguments: an array to be traversed and the name of a function to apply toeach key/value pair. (It also takes an optional third argument, discussed later in this section.) The function that is passed in to array_walk()should take two (or three) arguments. Thefirst argument will be the value of the array cell that is visited, and the second argument willbe the key of that cell. For example, here is a function that prints a descriptive statementabout the string length of an array value: function print_value_length($array_value, $array_key_ignored) { $the_length = strlen($array_value); print( The length of $array_value is $the_length
); } (Notice that this function intentionally does nothing with the second argument.) Now let spass this function over our standard sample array using array_walk(): array_walk($major_city_info, print_value_length ); which gives the browser output: The length of Caracas is 7The length of Venezuela is 9The length of Paris is 511 #BREAK# 174Part IPHP: The BasicsThe length of France is 6The length of Tokyo is 5The length of Japan is 5The final flexibility that array_walk()offers is accepting an optional third argument that, ifpresent, will be passed on, in turn, as a third argument to the function that is applied. Thisargument will be the same throughout the array s traversal, but it offers an extra source ofruntime control for the passed function s behavior. You should not alter an array while you are iterating through the array using array_walk(). There is no guarantee how array_walk()will behave if you do this. Table 9-2 shows a summary of the behavior of the array iteration functions that we covered inthis section. Notice that foreachand listare not included; they are not functions. Table 9-2: Functions for Iterating over ArraysFunctionArgumentsSide EffectReturn Valuecurrent()One array None.The value from the key/value argumentpair currently pointed to by theinternal current pointer (orfalse if no such value). next()One array Advances the pointer The value pointed to after the argumentby one. If already at the pointer has been advanced (or last element, it will move false if no such value). the pointer past the end, and subsequent calls to current()will return false. prev()One array Moves the pointer back The value pointed to after the argumentby one. If already at the pointer has been moved back first element, will move (or false if no such value). the pointer before the beginning. reset()One array Moves the pointer back to The first value stored in the argumentpoint to the first key/value array, or false for an empty pair, or before the array. beginning if the array is empty. end() One array Moves the pointer ahead The last value that is currently argumentto the last key/value pair.in the list of key/value pairs. pos()One array None. (This function is an The value of the key/value pair argumentalias for current().)that is currently pointed to. Caution11 #BREAK# 175Chapter 9Arrays and Array FunctionsFunctionArgumentsSide EffectReturn Valueeach()One array Moves the pointer ahead An array that packages the keys argumentto the next key/value pair.and values of the key/valuepair that was current beforethe pointer was moved (orfalse if no such pair). Thereturned array stores the keyand value under its own keys 0and 1, respectively, and alsounder its own keys key and value . array_walk()1) An array This function invokes the (Returns 1.) argument, function named by its 2) the name second argument on each of a two- key/value pair. Side (or three-) effects depend on the argument side effects of the function to passed function. call on each key/value, and 3) an optiona.l third argument. Extended Example: An Exercise CalculatorNow we ll continue our exercise calculator example that we started in Chapters 7 and 8 andmake further improvements using PHP arrays. When we left this example in Chapter 8, we were allowing the user to type the name of anexercise into a Web form, and we were hoping to match the submitted string with an exerciseknown to the receiving script. Instead, let s take our advice from late in Chapter 8 and con- strain inputs from the user to a set we know we can recognize on the receiving end. Listing 9-1 shows an HTML form that presents a set of exercises that the user can choosefrom. This uses a radio-button input, so that the user can choose only one exercise to submit. Listing 9-1:Entry form with radio buttons

Workout calculator (radio buttons with arrays)

Select one of the following exercises, and we ll tell you how long
you d have to do it to burn one pound of fat.


 Biking/cycling
 Running
 Soccer/football
 Stairclimber
 Weightlifting
 

#BREAK# 177Chapter 9Arrays and Array FunctionsFigure 9-2 shows how the entry form looks in a browser. Figure 9-2:Entry form with radio buttonsThe radio-button entry form passes a particular POSTvariable named exercise , which canhave several different numerical values depending on which button the user clicks on. Listing9-2 shows some handler code that can catch such a submission; the job of this code is to fig- ure out which button was clicked and to print some appropriate info, which has been storedin advance in arrays in the receiving code file. Listing 9-2:Handler for radio-button selection Biking/cycling , 1 => Running , 2 => Soccer/football , 3 => Stairclimber , 4 => Weightlifting ); // This is the array where we keep our duration data$duration_array = array( 0 => 5 hours and 40 minutes , 1 => 4 hours and 30 minutes , Continued11 #BREAK# 178Part IPHP: The BasicsListing 9-2(continued) 2 => 4 hours and 30 minutes , 3 => 5 hours , 4 => 7 hours and 30 minutes ); // Now pull out the chosen exercise from the submissionif (is_array($_POST) && count($_POST) > 1) { $exercise_value = $_POST[ exercise ]; $exercise_name = $name_array[$exercise_value]; $hours = $duration_array[$exercise_value]; } //Usually you d test an array for a count of 0, but here//there is 1 automatic POST element — $_POST[ submit ]. // Construct a sentence// ——————– if (isSet($hours)) { $message = It would take .$hours. of .$exercise_name. to burn one pound of fat. ; } else { // Hmmm, they didn t pick one or something odd happened$message = Ummm, did you pick an exercise? ; } // Now lay out the page// ——————– $page_str = <<< EOPAGE

Workout calculator handler (radio buttons with arrays)

The workout calculator says, $message

#BREAK# 179Chapter 9Arrays and Array Functions
EOPAGE; echo $page_str; ?> Figure 9-3 shows the result of catching the user submission and displaying appropriately. Figure 9-3:Displaying the result of radio-button entryAs usual, this is fine as far as it goes, but what if we want to submit more than one choice? For one thing, the point of the radio-button HTML construct is to allow only one choice, so wewill have to abandon it; for another, the receiving code is only set up to extract one exercise. To relax this constraint, let s use HTML check boxes instead of radio buttons for the formitself. And while we re doing that, let s use a nice feature of PHP form processing and give thechoices variable names that look like indexes into a single array variable (exercise[0], exercise[1], and so on). On the receiving end, we can simply treat exerciseas a singlearray that has arrived in the $_POST. Listing 9-3 shows the modified HTML form, and Listing9-4 shows the receiving code. #BREAK# 180Part IPHP: The BasicsListing 9-3:Entry form with check boxes

Workout calculator (multiple checkboxes with arrays)

Select one or more of the following exercises, and we ll tellyou
how long you d have to do each one to burn one pound offat.


 Biking/cycling
 Running
 Soccer/football
 Stairclimber
 Weightlifting
 
To save space, we ll skip the screenshots for this example; the submitting form looks similarto the radio-button example except for the prompting language, and the receiving page sim- ply prints multiple choices rather than a single choice. Listing 9-4:Handler for check boxes Biking/cycling , 1 => Running , 2 => Soccer/football , 3 => Stairclimber , 4 => Weightlifting ); // This is the array where we keep our duration data$duration_array = array( 0 => 5 hours and 40 minutes , 1 => 4 hours and 30 minutes , 2 => 4 hours and 30 minutes , 3 => 5 hours , 4 => 7 hours and 30 minutes ); // Now step through the exercises and see which ones they choseif (is_array($_POST) && count($_POST) > 1) { $message = ; foreach ($_POST[ exercise ] as $key => $val) { if ($val == 1) { $exercise_name = $name_array[$key]; $hours = $duration_array[$key]; $message .=

n

It would take $hours of $exercise_name . to burn one pound of fat. ; } } Continued11 #BREAK# 182Part IPHP: The BasicsListing 9-4(continued) } else { // Hmmm, they didn t pick one or something strange happened$message = Ummm, did you pick an exercise? ; } //If you don t have this test, an empty form will cause an // error. //Usually you d test an array for a count of 0, but here //there is 1 automatic POST array element — $_POST[ submit ]. // Now lay out the page// ——————– $page_str = <<< EOPAGE

Workout calculator handler (multiple checkboxes with arrays)

The workout calculator says: $message

EOPAGE; echo $page_str; ?> #BREAK# 183Chapter 9Arrays and Array FunctionsTaking this one step further, we can also submit multidimensional arrays via HTML form. All weneed to do is name our FORMvariables with multiple levels of brackets (like exercise[1][2]), and we ll have a multidimensional array on the receiving end. Listing 9-5 shows our slightly modified entry form, and Listing 9-6 shows receiving code thatiterates through the submitted multidimensional array, doing the appropriate unpacking. Listing 9-5:Entry form for multidimensional arrays

Workout calculator (multidimensional arrays)

Select one or more of the following exercises, and we ll tellyou
how long you d have to do each one to burn one pound of fat.


Continued11 #BREAK# 184Part IPHP: The BasicsListing 9-5(continued)

#BREAK# 185Chapter 9Arrays and Array Functions
Aerobic exercise
 Biking/cycling
 Rowing
 Running
 Stairclimber
 Walking
Sports
 Basketball
 Ice hockey
 Soccer/football
 Table tennis
Strength training
 Calisthenics
 Weightlifting (light)
 Weightlifting (strenuous)
Stretching/flexibility
 Pilates
 Tai chi
 Yoga
 

Figure 9-4 shows our new entry form, with check boxes (instead of radio buttons) to handlemultiple entries. Figure 9-4:Entry form using a multidimensional array11 #BREAK# 186Part IPHP: The BasicsListing 9-6:Handler for multidimensional array form Aerobic exercise , 1 => Sports , 2 => Strength training , 3 => Stretching/flexibility ); // This is the multidimensional array where we keep our // exercises$exercise_array = array(0 => array(0 => Biking/cycling , 1 => Rowing , 2 => Running , 3 => Stairclimber , 4 => Walking ), 1 => array(0 => Basketball , 1 => Ice hockey , 2 => Soccer/football , 3 => Table tennis ), 2 => array(0 => Calisthenics , 1 => Weightlifting (light) , 2 => Weightlifting (strenuous) , ), 3 => array(0 => Pilates , 1 => Tai chi , 2 => Yoga , ) ); // This is the array where we keep our duration data$duration_array = array(0 => array(0 => 5 hours and 40 minutes , 1 => 4 hours and 10 minutes , 2 => 4 hours and 30 minutes , 3 => 5 hours , 4 => 10 hours and 10 minutes ), 1 => array(0 => 5 hours , 1 => 5 hours , 2 => 4 hours and 30 minutes , 3 => 10 hours and 10 minutes ), 2 => array(0 => 6 hours and 30 minutes , 1 => 13 hours and 30 minutes , #BREAK# 187Chapter 9Arrays and Array Functions2 => 7 hours and 30 minutes , ), 3 => array(0 => 8 hours and 45 minutes , 1 => 10 hours and 10 minutes , 2 => 16 hours , ) ); // Now step through the exercises and see which one they choseif (is_array($_POST) && count($_POST) > 1&& is_array($_POST[ exercise ])) { $message = ; foreach ($_POST[ exercise ] as $key_1 => $val) { // $val should be an arrayif (!is_array($val)) { $message .= Something s wrong — value not array
; } else { // Add heading$heading = $exercise_types[$key_1]; $message .=

n

$heading ; foreach ($val as $key_2 => $val_2) { if ($val_2 == 1) { $exercise_name = $exercise_array[$key_1][$key_2]; $hours = $duration_array[$key_1][$key_2]; $message .=

n

It would take $hours of . $exercise_name to burn one pound of fat. ; } } } } } else { // Hmmm, they didn t pick one or something wack happened$message = Ummm, did you pick an exercise? ; } // Now lay out the page// ——————– $page_str = <<< EOPAGE Continued11 #BREAK# 188Part IPHP: The BasicsListing 9-6(continued)

Workout calculator handler (multidimensional arrays)

The workout calculator says: $message

EOPAGE; echo $page_str; ?> There are several layers of array packaging that the receiving code in Listing 9-6 pulls apart: First, it uses the exercise key to find the right value in the $_POSTarray. The value it findsis itself an array. It then uses two nested foreachloops to delve down through the two layersof indexing to the actual exercises submitted. Figure 9-5 shows the result. Figure 9-5:Displaying a multidimensional array submission11 #BREAK# 189Chapter 9Arrays and Array FunctionsSummaryThe array is a basic PHP datatype and plays the role of both record types and vector arraytypes in other languages. PHP arrays are associative, meaning that they store their values inassociation with unique keysor indices. Indices can be either strings or numbers, and aredenoted as indices by square brackets. (The expression $my_array[4]refers to the valuestored in $my_array in association with the integer index 4, and not necessarily to the 4thelement of $my_array.) The loose typing of PHP means that any PHP value can be stored as an array. In turn, thismeans that arrays can be stored as array elements. Multidimensional arraysare simply arraysthat contain other arrays as elements, with a reference syntax of successive brackets. (Theexpression $my_array[3][4]refers to the element (indexed by 4) of an array which is anelement (indexed by 3) of $my_array.) The array is the standard vehicle for PHP functions that return structured data, so PHP programmers should learn to unpack arrays, even if they are not interested in constructingthem. PHP also offers a huge variety of functions for manipulating data after you have itstored in an array, including functions for counting, summarizing, and sorting. … #BREAK# NumbersIf you need to do serious numerical, scientific, or statistical compu- tation, a Web-scripting language is probably not where you want tobe doing it. With that said, however, PHP does offer a generous arrayof functions that nicely cover most of the mathematical tasks thatarise in Web scripting. It also offers some more advanced capabilitiessuch as arbitrary-precision arithmetic and access to hashing andcryptographic libraries. The PHP designers have, quite sensibly, not tried to reinvent anywheels in this department. Instead, they found about eighteen per- fectly good wheels by the side of the road and built a lightweightfiberglass chassis to connect them all together. Many of the morebasic math functions in PHP are simple wrappers around their Ccounterparts (for more on this, see the sidebar A Glimpse behindthe Curtain in Chapter 27, which will cover PHP s mathematics capabilities in greater detail). Numerical TypesPHP has only two numerical types: integer(also known as long), anddouble(aka float), which correspond to the largest numerical types inthe C language. PHP does automatic conversion of numerical types, so they can be freely intermixed in numerical expressions and the right thing will typically happen. PHP also converts strings to numbers where necessary. In situations where you want a value to be interpreted as a particularnumerical type, you can force a typecast by prepending the type inparentheses, such as: (double) $my_var(integer) $my_varOr you can use the functions intval()and doubleval(), whichconvert their arguments to integers and doubles, respectively. For more details on the integer and double types, see Chapter 5.Cross- Reference1010CHAPTER …In This ChapterNumerical typesMathematical operatorsSimple math functionsRandom numbers …#BREAK# 192Part IPHP: The BasicsMathematical OperatorsMost of the mathematical action in PHP is in the form of built-in functions rather than in theform of operators. In addition to the comparison operators covered in Chapter 6, PHP offersfive operators for simple arithmetic, as well as some shorthandoperators that make incre- menting and assigning statements more concise. Arithmetic operatorsThe five basic arithmetic operators are those you would find on a four-function calculator, plus the modulus operator (%). (If you are unfamiliar with modulus, see the discussion follow- ing Table 10-1.) The operators are summarized in Table 10-1. Table 10-1: Arithmetic OperatorsOperatorBehaviorExamples+Sum of its two arguments.4+9.5evaluates to 13.5 If there are two arguments, the 50-75evaluates to -25right-hand argument is subtracted -3.9evaluates to -3.9from the left-hand argument. If there is just a right-hand argument, then the negative of that argument is returned. *Product of its two arguments.3.14*2evaluates to 6.28/Floating-point division of the left-hand 5/2evaluates to 2.5argument by the right-hand argument. %Integer remainder from division of 101%50evaluates to 1left-hand argument by the absolute 999%3evaluates to 0value of the right-hand argument. 43%94evaluates to 43(See discussion in the following-12%10evaluates to 2section.)-12%-10evaluates to -2Arithmetic operators and typesWith the first three arithmetic operators (+, -, *), you should expect type contagionfrom doubles to integers; that is, if both arguments are integers, the result will be an integer, but if either argument is a double, then the result will be a double. With the division operator, there is the same sort of contagion, and in addition the result will be a double if the divisionis not even. If you want integer division rather than floating-point division, simply coerce or convert thedivision result to an integer. For example, intval(5/2)evaluates to the integer 2. Tip12 #BREAK# 193Chapter 10NumbersModular arithmetic is sometimes taught in school as clock arithmetic. The process of takingone number modulo to another amounts to wrapping the first number around the second, or (equivalently) taking the remainder of the first number after dividing by the second. Theresult of such an operation is always less than the second number. Roughly speaking, a conventional civilian analog clock displays hours elapsed modulo 12, while military time is modulo 24. (The roughlyin the previous sentence is because the realmodulus function converts numbers to the range 0 to n-1, rather than the range 1 to n. If bell-tower clocks respected this, noontime would be marked by silence, rather than by twelvechimes.) The modulus operator in PHP (%) expects integer arguments if it is given doubles, they willsimply be converted to integers (by truncation) first. The result is always an integer. Most programming languages have some form of the modulus operator, but they differ in how they handle negative arguments. In some languages, the result of the operator is alwayspositive, and 2%26equals 24. In PHP, though, 2%26is 2, and, in general, the statement$mod=$first_num%$second_numis exactly equivalent to the expression: if ($first_num >= 0) $mod = $first_num % abs($second_num); else$mod = - (abs($first_num) % abs($second_num)); where abs()is the absolute value function. Incrementing operatorsPHP inherits a lot of its syntax from C, and C programmers are famously proud of their ownconciseness. The incrementing/decrementing operators taken from C make it possible tomore concisely represent statements like $count = $count + 1, which tend to be typed frequently. The increment operator (++) adds one to the variable it is attached to, and the decrementoperator (–) subtracts one from the variable. Each one comes in two flavors, postincrement(which is placed immediately after the affected variable), and preincrement(which comesimmediately before). Both flavors have the same side effect of changing the variable s value, but they have different values as expressions. The postincrement operator acts as if itchanges the variable s value after the expression s value is returned, whereas the preincre- ment operator acts as though it makes the change first and then returns the variable s newvalue. You can see the difference by using the operators in assignment statements, like this: $count = 0; $result = $count++; print( Post ++: count is $count, result is $result
); $count = 0; $result = ++$count; print( Pre ++: count is $count, result is $result
); $count = 0; $result = $count–; print( Post –: count is $count, result is $result
); $count = 0; $result = –$count; print( Pre –: count is $count, result is $result
); #BREAK# 194Part IPHP: The Basicswhich gives the browser output: Post ++: count is 1, result is 0Pre ++: count is 1, result is 1Post –: count is -1, result is 0Pre –: count is -1, result is 1In this example, the statement $result=$count++;is exactly equivalent to$result = $count; $count = $count + 1; while $result=++$count;is equivalent to$count = $count + 1; $result = $count; Assignment operatorsIncrementing operators like ++save keystrokes when adding one to a variable, but they don thelp when adding another number or performing another kind of arithmetic. Luckily, all fivearithmetic operators have corresponding assignment operators (+=, -=, *=, /=, and %=) thatassign to a variable the result of an arithmetic operation on that variable in one fell swoop. The statement: $count = $count * 3; can be shortened to: $count *= 3; and the statement: $count = $count + 17; becomes: $count += 17; Comparison operatorsPHP includes the standard arithmetic comparison operators, which take simple values (num- bers or strings) as arguments and evaluate to either TRUEor FALSE: For examples of using the comparison operators and also some gotcha issues with comparingdoubles and strings, see Chapter 6. .The <(less than) operator is true if its left-hand argument is strictly less than its right- hand argument but false otherwise. .The >(greater than) operator is true if its left-hand argument is strictly greater than itsright-hand argument but false otherwise. .The <=(less than or equal) operator is true if its left-hand argument is less than orequal to its right-hand argument but false otherwise. .The >=(greater than or equal) operator is true if its left-hand argument is greater thanor equal to its right-hand argument but false otherwise. Cross- Reference12 #BREAK# 195Chapter 10Numbers .The ==(equal to) operator is true if its arguments are exactly equal but false otherwise. .The !=(not equal) operator is false if its arguments are exactly equal and true otherwise. .The ===operator (identical to) is true if its two arguments are exactly equal and of thesame type. The identical tooperator (===)can, at times, be a necessary antidote to PHP s auto- matic type conversions. None of the following expressions will have a truevalue: 2 === 2.02 === 2 2.0 === 2.00 === FALSEThis behavior can be invaluable, for example, if you have a function that returns a stringwhen it succeeds (which might be the empty string) and a FALSEvalue when it fails. Testingthe truth of the return value would confuse FALSEwith the empty string, whereas the iden- tical operator can distinguish them. Precedence and parenthesesOperator precedencerules govern the relative stickiness of operators, deciding which opera- tors in an expression get first claim on the arguments that surround them. You can find acomplete table of all operator precedences in the manual at www.php.net, but the importantprecedence rules for arithmetic are: .Arithmetic operators have higher precedence (that is, bind more tightly) than compari- son operators. .Comparison operators have higher precedence than assignment operators. .The *, /, and %arithmetic operators have the same precedence. .The +and arithmetic operators have the same precedence. .The *, /, and %operators have higher precedence than +and . .When arithmetic operators are of the same precedence, associativity is left-to-right(that is, a number will associate with an operator to its left in preference to the opera- tor on its right). If you find the precedence rules difficult to remember, the next person who reads your codemay have the same problem, so feel free to parenthesize when in doubt. For example, can youeasily figure out the value of this expression? 1 + 2 * 3 - 4 - 5 / 4 % 3As it turns out, the value is 2, as we can see more easily when we add parentheses that arenot, strictly speaking, necessary: ((1 + (2 * 3)) 4) ((5 / 4) % 3) Tip12 #BREAK# 196Part IPHP: The BasicsSimple Mathematical FunctionsThe next step up in sophistication from the arithmetic operators consists of miscellaneous func- tions that perform tasks like converting between the two numerical types (which we discussedin Chapter 5) and finding the minimum and maximum of a set of numbers (see Table 10-2). Table 10-2: Simple Math FunctionsFunctionBehaviorfloor()Takes a single argument (typically a double) and returns the largest integerthat is less than or equal to that argument. ceil()Short for ceiling takes a single argument (typically a double) and returns thesmallest integer that is greater than or equal to that argument. round()Takes a single argument (typically a double) and returns the nearest integer. Ifthe fractional part is exactly 0.5, it returns the nearest even number. abs()Short for absolute value if the single numerical argument is negative, thecorresponding positive number is returned; if the argument is positive, theargument itself is returned. min()Takes any number of numerical arguments (but at least one) and returns thesmallest of the arguments. max()Takes any number of numerical arguments (but at least one) and returns thelargest of the arguments. For example, the result of the following expression: min(3, abs(-3), max(round(2.7), ceil(2.3), floor(3.9))) is 3, because the value of every function call is also 3. RandomnessPHP s functions for generating pseudo-random numbers are summarized in Table 10-3. (If youare new to random number generation and are wondering what the pseudois all about, pleasesee the accompanying sidebar.) There are two random number generators (invoked with rand()and mt_rand(), respectively), each with the same three associated functions: a seeding function, the random-number functionitself, and a function that retrieves the largest integer that might be returned by the generator. The particular pseudo-random function that is used by rand()may depend on the particularlibraries that PHP was compiled with. By contrast, the mt_rand()generator always uses the12 #BREAK# 197Chapter 10Numberssame random function (the Mersenne Twister), and the author of mt_rand() s online documen- tation argues that it is also faster and more random (in a cryptographic sense) than rand(). We have no reason to believe that this is not correct, so we prefer mt_rand()to rand(). Table 10-3: Random Number FunctionsFunctionBehaviorsrand()Takes a single positive integer argument and seeds the random numbergenerator with it. rand()If called with no arguments, returns a random number between 0 andRAND_MAX(which can be retrieved with the function getrandmax()). The function can also be called with two integer arguments to restrict therange of the number returned the first argument is the minimum andthe second is the maximum (inclusive). getrandmax()Returns the largest number that may be returned by rand(). This islimited to 32768 on Windows platforms. mt_srand()Like srand(), except that it seeds the better random number generator. mt_rand()Like rand(), except that it uses the better random number generator. mt_getrandmax()Returns the largest number that may be returned by mt_rand(). On some PHP versions and some platforms, you can apparently get seemingly random num- bers from rand()and mt_rand()without seeding first this should not be relied upon, however, both for reasons of portability and because the unseeded behavior is not guaranteed. Seeding the generatorThe typical way to seed either of the PHP random-number generators (using mt_srand()orsrand()) looks like this: mt_srand((double)microtime()*1000000); This sets the seed of the generator to be the number of microseconds that have elapsed sincethe last whole second. (Yes, the typecast to doubleis necessary here, because microtime() returns a string, which would treated as an integer in the multiplication but for the cast.) Please use this seeding statement even if you don t understand it just place it in any PHPpage, once only, before you use the corresponding mt_rand()or rand()functions, and it willensure that you have a varying starting point and therefore random sequences that are differ- ent every time. This particular seeding technique has been thought through by people whounderstand the ins and outs of pseudo-random number generation and is probably betterthan any attempt an individual programmer might make to try something trickier. Note12 #BREAK# 198Part IPHP: The BasicsHere s some representative code that uses the pseudo-random functions: print( Seeding the generator
); mt_srand((double)microtime() * 1000000); print( With no arguments: . mt_rand() .
); print( With no arguments: . mt_rand() .
); print( With no arguments: . mt_rand() .
); print( With two arguments: . mt_rand(27, 31) .
); print( With two arguments: . mt_rand(27, 31) .
); print( With two arguments: . mt_rand(27, 31) .
); with the browser output: Seeding the generatorWith no arguments: 992873415With no arguments: 656237128With no arguments: 1239053221With two arguments: 28With two arguments: 31With two arguments: 29Pseudo-random Number GeneratorsAs with all programming languages, the random number functions offered by PHP are reallyimplemented by pseudo-random number generators. This is because conventional computerarchitectures are deterministic machines that will always produce the same results given thesame starting conditions and inputs and have no good source of randomness. (Here we re talk- ing about the ideal computer as it is supposed to work, not the actual physically embodied, power-interruptible, cosmic-ray flippable, seemingly very random machines we all struggle withdaily!) You could imagine connecting a conventional computer to a source of random bits suchas a mechanical coin-flip reader, or a device that observed quantum-level events, but suchperipherals don t seem to be widely available at this time. So we must make do with pseudo-random generators, which produce a deterministic sequenceof numbers that looks random enough for most purposes. They typically work by running theirinitial input number (the seed) through a particular mathematical function to produce the firstnumber in the sequence; each subsequent number in the sequence is the result of applying thatsame function to the previous number in the sequence. The sequence will repeat at some point(once it generates a particular number for the second time, it is doomed to follow the samesequence as it did the first time around), but a good iteration function will generate a very longsequence of numbers that have little apparent pattern before the loop occurs. How do you choose a seed to start off with? Because of the generator s determinism, if you hard- code a PHP page to have a particular seed, that page will always see the same sequence fromthe generator. (Although this is not usually what you want, it can be an invaluable trick when youare trying to debug behavior that depends on the particular numbers that are generated.) Thetypical seeding technique is to use a fast-changing digit from the system clock as the initialseed although those numbers are not exactly random, they are likely to vary quickly enoughthat subsequent page executions will start with a different seed every time. #BREAK# 199Chapter 10NumbersAlthough the random-number functions only return integers, it is easy to convert a randominteger in a given range to a corresponding floating-point number (say, one between 0.0 and1.0 inclusive) with an expression like rand()/getrandmax(). You can then scale andshift the range as desired (to, say, a number between 100.0 and 120.0) with an expressionlike 100.0+20.0*(rand()/getrandmax()). Obviously, if you run exactly this code, you will get numbers that differ from those in the output shown here, because the point of seeding the generator this way is to ensure that different executions produce different sequences of numbers. In some old versions of PHP3, the rand()function buggily ignored its arguments, returningnumbers between 0and getrandmax()regardless of restrictions. We have also heardsome reports of that behavior under more recent Windows implementations. If you suspectthat you are suffering from such a bug, you can define your own restricted version of rand() like so: function my_rand ($min, $max) { return(rand() % (($max - $min) + 1) + $min); } Unlike rand(), this version requires the minand maxarguments. Example: Making a random selectionNow let s use the random functions for something useful (or, at least, something that could beused for something useful). The following two functions let you construct a random string ofletters, which could, in turn, be used as a random login or password string: function random_char($string) { $length = strlen($string); $position = mt_rand(0, $length - 1); return($string[$position]); } function random_string ($charset_string, $length) { $return_string = ; // the empty stringfor ($x = 0; $x < $length; $x++) $return_string .= random_char($charset_string); return($return_string); } The random_char()function chooses a character (or, actually, a substring of length 1) fromits input string. It does this by restricting the mt_rand()function to positions within thelength of the string (with chars numbered starting at zero), and then returning the characterthat is at that random position. The random_string()function calls random_char()a number of times on a string representing the universe of characters to be chosen from andconcatenates a string of the desired length. CautionTip12 #BREAK# 200Part IPHP: The BasicsNow, to demonstrate this code, we first seed the generator, define our universe of allowablecharacters, and then call random_string()a few times in a row: mt_srand((double)microtime() * 1000000); $charset = abcdefghijklmnopqrstuvwxyz ; $random_string = random_string($charset, 8); print( random_string: $random_string
); $random_string = random_string($charset, 8) ; print( random_string: $random_string
); $random_string = random_string($charset, 8) ; print( random_string: $random_string
); with the result: random_string: eisexkiorandom_string: mkvflwfyrandom_string: gpulbwthIn this example, we seed the generator only once, and we draw that seed value from the sys- tem clock. Notice what happens if we make the mistake of repeatedly seeding the generatorwith the same value: mt_srand(43); $random_string = random_string($charset, 8) ; print( random_string: $random_string
); mt_srand(43); $random_string = random_string($charset, 8) ; print( random_string: $random_string
); mt_srand(43); $random_string = random_string($charset, 8) ; print( random_string: $random_string
); Because the sequence that is generated depends deterministically on the seed, we get thesame behavior each time: random_string: qgkxvurwrandom_string: qgkxvurwrandom_string: qgkxvurwIn these examples, we chose to draw random characters from strings, but this kind of selec- tion process is generalizable to draw items from arrays or to be used in any situation thatrequires choosing random members from a set. All you need is the universe of items, a way toput them in numerical order, and a way to retrieve them by order number, and you can thenuse the rand()or mt_rand()function to choose a random order number for the retrieval. Extended Example: An Exercise CalculatorNow let s return to the exercise-calculator example that we ve been developing sinceChapter7. In addition to mixing in a little bit of arithmetic calculation, we ll reorganize thecode a bit. One problem with the code as we left it was that we had our data in two different code filesand in more than one array. Changes to the data require updating more than one file and takesome care to make sure that everything stays in sync. #BREAK# 201Chapter 10NumbersWe could always keep this data in a text file or in a database. For this chapter, however, let sjust make a single PHP code file where we define a single array with everything we need: types of exercises, names of exercises, and the calories per minute that each exercise con- sumes, assuming a person of average weight. Such an array is shown in Listing 10-1; we ll call this file exercise_include.php. Listing 10-1:exercise_include.php array( biking/cycling => 9, rowing => 8, running => 14, stairclimber => 6, walking => 5), Sports => array( basketball => 12, ice hockey => 9, soccer/football => 11, table tennis => 7), Strength training => array( calisthenics => 11, weightlifting (light) => 9, weightlifting (strenuous) => 13), Stretching/flexibility => array( pilates => 5, tai chi => 6, yoga => 5) ); ?> Although we ll stick to having separate form submission and form handler pages, let s makethe form submission page a PHP file too, rather than using straight HTML. This will let us generate the form elements from the data we defined in exercise_include.php. Listing 10-2:Form submission code for fitness calculator

Workout calculator (math)

For one or more of the following exercises, enter
the duration in minutes and your current weight
and we ll tell you how many calories you burned.

$per_exercise_info) { print(

); $exercise_counter = 0; foreach ($per_exercise_info as $exercise_name => $exercise_intensity) { print(

); $exercise_counter++; } $type_counter++; } ?>

#BREAK# 203Chapter 10Numbers
Weight (kilos)
 
$exercise_type
 $exercise_name
 

Although mostly a static HTML page, the hierarchy of exercises specified in exercise_ include.phpcan be used to print out the data-entry portion of the form. Notice that we printthe names and types of exercises but do not propagate them as form variables; we plan touse the very same array from exercise_include.phpon the other end to distinguish themeaning of the submitted variables. The submission form itself is shown in Figure 10-1. Figure 10-1:The calculator entry formNow the job of the receiving page is more complex than in previous chapters. We will bereceiving an array in $_POST[ exercise ]that has a hierarchical structure similar to theone defined in exercise_include.php. #BREAK# 204Part IPHP: The BasicsThe difference is that the former includes the minutes spent at each exercise, while the latterincludes the rate at which that exercise burns calories. The receiving page s job is to use botharrays to produce a report for the user about calories actually burned. The handler code is shown in Listing 10-3. First of all, we receive the submitted weight. Thenwe move on to iterating through the data array defined in exercise_include.phpandquerying the $_POST[ exercise ]array for nonzero minutes submitted for each exercise. We re relying on the fact that the iteration uses the same data array and does the same count- ing in both the sending and receiving pages; this means that if we find nonzero minutes in agiven position, we can correlate that with the name, type, and calories/minute drawn fromthe data file. Whenever we find a hit (some positive minutes entered for an exercise we know about), wesimply calculate the calories burned (calories/minute minutes) and then adjust that valuefor the user s weight. We round each value before adding it into a total, largely so that theindividual entries will agree with the rounded sum. Listing 10-3:Handler code for the fitness calculator(wc_handler_math.php) 1&& is_array($_POST[ exercise ])) { foreach ($exercise_info as $exercise_type => $per_exercise_info) { $exercise_counter = 0; foreach ($per_exercise_info as $exercise_name => $exercise_intensity) { $minutes = $_POST[ exercise ][$type_counter][$exercise_counter]; if ($minutes > 0) { $exercise_accumulator[$exercise_type][$exercise_name] = round($minutes * $exercise_intensity * $weight_factor); } $exercise_counter++; } $type_counter++; } } // now we use $exercise_accumulator to build a display string$total_calories = 0; $message = ; foreach ($exercise_accumulator12 #BREAK# 205Chapter 10Numbersas $exercise_type => $per_exercise_info) { $message .=

$exercise_type

; foreach ($per_exercise_info as $exercise_name => $calories_burned) { $message .=

. ucfirst( $exercise_name: $calories_burned calories

); $total_calories += $calories_burned; } } if ($message == || $weight == 0) { $message =

Did you enter your weight and at least one exercise? ; } else { $message .=

Total calories burned: $total_calories

; } // Now lay out the page// ——————– $page_str = <<< EOPAGE

Workout calculator handler (Math)

The workout calculator says: $message

EOPAGE; echo $page_str; ?> #BREAK# 206Part IPHP: The BasicsThe resulting handler page is shown in Figure 10-2. Figure 10-2:Result page for the fitness calculatorIn addition to doing more numeric calculation than our calculator from previous chapters, this version does a better job of splitting up data and code, at the cost of some extra complexity. SummaryThe highlights of PHP math are summarized in Table 10-4. Refer to Chapter 27 for moreadvanced mathematical concepts as they are handled by PHP. Table 10-4: Summary of PHP Math Operators and FunctionsCategoryDescriptionArithmetic operatorsOperators +, -, *, /, %perform basic arithmetic on integers anddoubles. Incrementing operatorsThe ++and –operators change the values of numerical variables, increasing them by one or decreasing them by one (respectively). Thevalue of the postincrement form ($var++)is the same as thevariable s value before the change; the value of the preincrement form(++$var)is the variable s value after the change. #BREAK# 207Chapter 10NumbersCategoryDescriptionAssignment operatorsEach arithmetic operator (like +) has a corresponding assignmentoperator (+=). The expression $count += 5is equivalent to$count = $count + 5. Comparison operatorsThese operators (<, <=, >, >=, ==, !=) compare two numbers andreturn either trueor false. The ===operator is true if and only ifits arguments are equal and of the same type. Basic math functionsfloor(), ceil(), and round()convert doubles to integers, min()and max()take the minimum and maximum of theirnumerical arguments, and abs()is the absolute value function. … #BREAK# Basic PHP GotchasEven though we ve tried to give clear instructions, and you ve nodoubt followed them to the letter, there are still many potentialglitches that can arise. This chapter will lay out some of the mostcommon problems by symptom and suggest some frequent causes. There is a whole other universe of gotchas involving database con- nectivity. This chapter deals with PHP-only problems. You maywant to skip ahead to Chapter 19 if you re having problems withPHP and a database. Also, problems specific to certain more- advanced features (including sessions, cookies, building graphics, e-mail, and XML) are dealt with in their individual chapters in PartsIII and IV. Installation-Related ProblemsInstead of getting moralistic about people who rush through theirinstalls without understanding the documentation, we ll point out afew common symptoms that characteristically appear when you vejust installed PHP for the first time. If you are seeing similar errors but are confident that your installa- tion is stable, follow the cross-references to later parts of thischapter. Symptom: Text of file displayed inbrowser windowIf you are seeing the text of your PHP script instead of the resultingHTML, the PHP engine is clearly not being invoked. Check that youare accessing the site by invoking the httpd, not via the filesystem. Do this: http://localhost/mysite/mypage.phprather than this: file:/home/httpd/html/mysite/mypage.phpTipCross- Reference1111CHAPTER …In This ChapterInstallation-relatedproblemsRendering problemsFailures to load pageParse errorsFile permissionsMissing includesUnbound variablesFunction problemsMath problemsTime-outs …#BREAK# 210Part IPHP: The BasicsSymptom: PHP blocks showing up as text under HTTP or browser prompts you to save file The PHP engine is not being invoked properly. If you re properly requesting the file via HTTPas explained previously, the most common reason for this error is that you haven t specifiedall the filename extensions you want PHP to recognize, at least not for this directory. Go backto Chapter 3 and review how to configure your Web server to recognize PHP file extensions. The second most common reason is that your php.inifile is in the wrong place or has a badconfiguration directive. If you see PHP code in your Web browser and you have a stable installation, your problem isprobably due to missing PHP tags. See the Rendering Problems section later in this chapter. Symptom: Server or host not found/ Page cannot be displayedIf your browser can t find your server, you may have a DNS (Domain Name Service) or Web-server configuration issue. If you can get to the site via IP address rather than domain name, your problem is probablyDNS-related. Maybe your DNS alias hasn t propagated throughout the Internet yet. This problem does occur occasionally even after the site has been up for awhile, either becauseyour DNS server goes down without a valid secondary server or because of local Internetconditions. If you cannot get to the site via IP address for a new installation, it s likely you haven t success- fully bound the IP address to your network interface or configured httpdto handle requestsfor a particular domain (see Chapter 3). If you can t get to the site via IP address for a previ- ously working installation, most likely your Web server is down for a non-PHP related reason. This can happen even on stable installations if, for instance, the server rebooted unexpect- edly and the Web service is not included properly in startup scripts. Rendering ProblemsThis section covers problems where PHP does not report an error per se, but what you see isnot what you thought you would get. Symptom: Totally blank pageA blank page is very frequently an HTML problem rather than PHP per se (except insofar asyou use PHP to produce HTML). If you do notuse the maximal style of PHP (in other words, ifthere is any part of your script that should be renderable without first being preprocessed), the problem is almost sure to be in the HTML. So you should first try doing whatever youusually do to debug HTML. In general, one of your best debugging tools when faced with puzzling browser output is simply to view the HTML source that the browser is trying to render. All browsers have somecommand for viewing such a source. For example, in Internet Explorer, it is the Source selec- tion under the View menu. Cross- Reference13 #BREAK# 211Chapter 11Basic PHP GotchasIf you wrote the file using a plain text editor, quickly check to make sure you haven t left outsomething crucial, such as a closing or tag. If you used a WYSIWYG editorat some stage, the problem is more likely to be an extra element of some kind. You may get edifying results by viewing the HTML source from a client (especially if you use amaximal PHP style) or from a different browser. Internet Explorer is supposedly the most for- giving of mistakes, whereas Opera and Amaya are the strictest at enforcing HTML style. Although client rendering of a page occurs independently of anything you might do withPHP, it s a good idea to preview output for all of the reasons mentioned previously, andmany of the reasons to follow, in a variety of browsers. Opera is increasingly popular, since itis now available in a free version, and the masochist in you will have lots of fun trying towrite a page that Amaya feels is acceptable AND renders the way you want. Sometimes a page that appears blank in your browser is blank because there is simply noHTML to display. See the next symptom for possible causes of this. Symptom: Document contains no dataIn some situations your Web server may return no HTML whatsoever in response to a request. The exact symptom that presents when this happens varies by browser. In some versions ofNetscape, for example, you will see a pop-up dialog that informs you that This documentcontained no dataand urges you to get professional help. IE5, on the other hand, willcheerfully display an empty page. If you view the HTML source, you may see nothing or youmay see an autogenerated minimal set of HTML headers (an empty head or an empty body). But any way you slice it, if PHP isn t sending back any HTML, you re not going to see anythinginteresting in your browser. One possible answer in this case is that the PHP module is not working at all. Test by browsinga different page in the same directory that you ve previously verified is being correctly han- dled by PHP. If you are a developer who does not maintain your own site, you may need totalk to your system administrator. If other pages work, and this one doesn t, the problem islikely to be with your code. Another possible answer is that your code really is not generating any output. For example, loading a PHP code file that contains nothing but function definitions in PHP mode would giveyou this kind of problem. Make sure you have an output directive in the file you re trying tolook at (echo, print, printf, print_r, or var_dump). A third possibility is that your code is actually making the PHP invocation crash before it candeliver any output. For example, if you define a recursive function that doesn t have a basecase, like so: function recurse_forever() { // don t do this! recurse_forever(); } recurse_forever(); PHP will try to execute the function until it runs out of memory, will crash in short order, andwill return nothing. A diagnostic for this kind of problem is to put some kind of print statementat the beginning of your PHP code (at least, if you do not have access to a debugger). If theprint statement executes, and PHP is not blowing up, you should be able to see your printedstring, regardless of what happens later if not in the browser, then in the HTML source. IfTip13 #BREAK# 212Part IPHP: The Basicsthe string does not appear at all, it probably means that PHP died after encountering theprintstatement, but before actually shipping off any output. (In addition to infinitely recurs- ing functions, we ve seen this kind of behavior when using early versions of object serializa- tion.) Strategically placed printstatements are probably one of the best debugging tools atyour disposal. Remember this for your later efforts. Also see the Time-outs section near the end of this chapter for more information on whathappens when you write code that runs forever. Finally, you might be seeing a blank screen if your PHP hits a more or less fatal error but youhave error reporting turned off. Error reporting should probably be turned off for productionservers for security reasons, but error reporting to the browser is actually a huge help fordevelopment servers. Check your php.inifile s error reporting section and make sure thesettings are what you expected. If you really dislike error reporting to the browser, you needto make heavy use of the error_logfunction in exception handling. See Chapters 31 and 32for more debugging tips. Symptom: Incomplete or unintended pageThese problems are usually in the HTML parts of the script. Figure 11-1 shows an interestingexample, which is highly browser-dependent; this is the IE5 product. Figure 11-1:An incomplete or unintended HTML result13 #BREAK# 213Chapter 11Basic PHP GotchasView the HTML source from a client. Sometimes the source will break off at the problematicpoint. If the source doesn t conveniently break off, try putting temporary error messages (inHTML mode) in different parts of the script to narrow down the location of the breakdownpoint, like this: Is the glitch in position 1? . Or position 3? This test would show the result seen in Figure 11-2, indicating that the temporary error mes- sages in positions 1 and 3 are showing up in the right places relative to the other elements. It sposition 2 that s out of place, indicating a likely problem (lack of a
Position 2? What we have here is
tag) with this line. Figure 11-2:Using temporary HTML error messages13 #BREAK# 214Part IPHP: The BasicsYour page may be incomplete because of a complete lack of PHP preprocessing, as in a scriptlike this:

What we have here is. This script will show up as seen in Figure 11-3. Figure 11-3:Failure to be preprocessedIn other words, all HTML-mode stuff will show up, but no PHP-mode stuff or error messageswill appear. This is indicative of the PHP module not working at all or of the page residing ona computer without a PHP-enabled Web server. (Don t laugh. It happens a lot when you forgetthat you ve been working on a particular version of a page on your client.) Symptom: PHP code showing up in Web browserIf you are seeing literal PHP code in your browser, rather than a rendering of the HTML itshould be producing, probably you have omitted a PHP start tag somewhere. (This assumesthat you have had PHP running successfully and that you are using the correct tags for yourinstallation. If not, see the Installation-Related Problems section near the beginning of thischapter.) It s easy to forget that PHP treats included files as HTML, not as PHP, unless you tell it other- wise with a start tag at the beginning of the file. For example, assume that we load the follow- ing PHP file: #BREAK# 215Chapter 11Basic PHP Gotchaswhich includes the file secret.php, which in turn looks like this: function secret_function () { echo Open sesame! ; } The result is shown in Figure 11-4. Figure 11-4:A PHP include appearing as HTMLThis can be fixed by adding PHP tags to the included file like so: Failures to Load PageA couple of different kinds of errors are seen when PHP is unable to find a file that you haveasked it to load. Symptom: Page cannot be foundIf your browser can t find a PHP page you ve created, and you have recently installed PHP, please see the section Installation-Related Problems earlier in this chapter. If you get thismessage when you have been loading other PHP files without incident, it s quite likely you arejust misspelling the filename or path. Alternatively, you may be confused about where the Webserver document root is located. #BREAK# 216Part IPHP: The BasicsSymptom: Failed opening [file] for inclusionWhen including files from PHP files, we sometimes see errors like this (on a Unix platform, thefile paths would be slightly different): Warning Failed opening C:InetPubwwwrootasdf.php forinclusion (include_path= ) in [no active file] on line 0It turns out that this is the included-file version of Pagecannotbefound that is, PHP hasn teven gotten to loading the first line of the active file. There is no active file because no file bythat name could be found. It s also possible that you will see this message as a result of incorrect permissions on the fileyou are trying to load. Parse ErrorsThe most common category of error arises from mistyped or syntactically incorrect PHP code, which confuses the PHP parsing engine. Symptom: Parse error messageAlthough the causes of parsing problems are many, the symptom is almost always the same: aparse error message like that in Figure 11-5. Figure 11-5:A parse error messageThese errors occur in PHP mode by definition. This is actually good, because PHP returnsmore informative error messages than HTML notably the line number of the problematicparsable. The most common causes of parse errors, detailed in the subsections that follow, are all quiteminor and easy to fix, especially with PHP lighting the way for you. However, every parse errorreturns the identical message (except for filenames and line numbers) regardless of cause. Any HTML that may be in the file, even if it appears before the error-causing PHP fragment, will not be displayed or appear in the source code. #BREAK# 217Chapter 11Basic PHP GotchasThe missing semicolonIf each PHP instruction is not duly finished off with a semicolon, a parse error will result. Inthis sample fragment, the first line lacks a semicolon and, therefore, the variable assignmentis never completed. What we have here is. No dollar signsAnother very common problem is that a dollar sign prepending a variable name is missing. If the dollar sign is missing during the initial variable assignment, like so: What we have here is. a parse error message will result. However, if instead the dollar sign is missing from a lateroutput of the variable, like this: What we have here is. PHP will not indicate a parse error. Instead, you will get the screen shown in Figure 11-6. Figure 11-6:A missing dollar sign on variable outputThis is an excellent example of why you should not rely on PHP to tell you something iswrong. Although PHP s error messages are more informative than most, errors such as thisare easily missed if your proofreading efforts aren t up to par. #BREAK# 218Part IPHP: The BasicsIf you spend any significant portion of your time debugging PHP code, an editor that canjump to specific line numbers can be invaluable. Note that the actual mistake that causedthe error may be on the line that PHP complains about, or before it, but never after it. Forexample, because there s nothing wrong with commands that span several lines, a missedsemicolon won t cause a parse error until PHP tries to interpret subsequent lines as part ofthe same statement. Mode issuesAnother family of glitches arises from faulty transitions in and out of PHP mode. A parse error will result if you fail to close off a PHP block properly, as in: What we have here isafter the first curly brace, when we intend to return to HTML mode) will return a parse error: . Another instance of a very common problem is this one, which combines the short block andweaving-in-and-out-of-HTML issues neatly:

> >
A PHP double-quote and the HTML closing bracket have been forgotten on the PhoneNumberinput line here. This will both cause a parse error and prevent the Submit button from dis- playing on a client browser. Tip13 #BREAK# 219Chapter 11Basic PHP GotchasThe sample code is meant to demonstrate how easy it can be to forget an element on a crowdedpage with lots of small but important symbols. You can reduce this type of error by either usinga good programmer s text editor or by completing and testing the HTML first and adding thePHP later (or both). Unescaped quotesAnother type of parse error is characteristic of maximal PHP: the unescaped quote. . In this case, the double-quote just before the word Whatis incorrectly, and therefore ineffec- tively, escaped by a forward slash rather than a backslash. If you simply forgot the backslash, the effect would be the same. Unterminated stringsFailing to close off a quoted string can cause parse errors that refer to line numbers far awayfrom the source of the problem. For example, a code file like this: print( I am a guilty print statement!); // line 5// 47 lines of PHP code omitted … print( I am an innocent print statement! ); // line 53might well produce a parse error that complains about line 53. This is because PHP is happyto include any text you might want in a quoted string, including many lines of your own code. This inclusion finishes happily with the first double-quote in line 53, and then the parser findsthe symbol I, which it can t figure out how to interpret as PHP code. If the quote symbol that begins the unterminated string happens to be the last one in the file, the line number in the complaint will be the last line in the file again, probably far awayfrom the scene of the crime. Other parse error causesThe problems we have named are not an exhaustive list of the sources of parse errors. Anything that makes a PHP statement malformed will confuse the parser, including unclosedparentheses, unclosed brackets, operators without arguments, control structure tests withoutparentheses, and so on. Sometimes the parse error will include a statement about what PHPwas expecting and didn t find, which can be a helpful clue. If the line of the parse error is thevery last line of the file, it usually means that some kind of enclosure (quotes, parentheses, braces) was opened and never closed, and PHP kept on hoping until the very end. File PermissionsMost operating systems have some scheme of file and directory permissions that specifieswhich users have what kind of access to which files. The Web server runs as some user underthis system and must have read permission for any files it looks at, including HTML and PHPsource files. #BREAK# 220Part IPHP: The BasicsSymptom: HTTP error 403When a browser page presents you with error 403, it means that your file permissions areincorrect. Some browsers will not mention the error number but will complain that you donot have access to the given Web page. The most common reason for this is that you haven t made this directory world-executable(Unix) or enabled script execution (Windows). Remember that PHP scripts may run under auser ID different than your own. Under Unix, PHP usually inherits the nobody UID, which(we hope) is pretty much restricted to the HTTP service. Under some Windows installations, each HTTP request is logged as the anonymous guest user. Missing IncludesIn addition to loading top-level source files, PHP needs to be able to load any files you bringin via include()or require(). Symptom: Include warningThis kind of error is shown in Figure 11-7. Figure 11-7:Include warningThe problem is that you call somewhere in the script for a file to be included, but PHP can tfind it. Check to see that the path is correct. You might also have a case sensitivity or othertypographic issue. You will also get this message if your script tries to include a file that is in another directorywith incorrect permissions for the PHP user. Generally, the directory must be specificallyreadable and executable by the Web-server user (often nobody under Unix) or generallyworld-readable (for example, 755 under Unix systems); and of course the file must be world- readable (at least 444). It s useful to reference the path of your included files with the$_SERVERarray. This will help to insure your included file is found and also that the scriptwhich calls it is portable especially important if you use a test server or special stagingarea to develop your pages: include_once ($_SERVER[DOCUMENT_ROOT] . include_file.php ); #BREAK# 221Chapter 11Basic PHP GotchasUnbound VariablesPHP is different from many programming languages in that variables do not have to bedeclared before being assigned, and (under its default settings) PHP will not complain if theyare used before being assigned (or bound) either. As a result, forgetting to assign a variablewill not result in direct errors either you will see puzzling, but error-free output, or you willsee a downstream error that is a result of variables not having the values you expected. (Ifyou would rather be warned, you can set the error-reporting level in php.inior by evaluat- ing error_reporting(E_ALL).) Some symptoms of this kind of problem follow. Symptom: Variable not showing up in print stringIf you embed a variable in a double-quoted string ( like $this ) and then print the stringusing printor echo, the variable s value should show up in the string. If it seems to not bethere at all in the output ( like ), the variable has probably never been assigned. Symptom: Numerical variable unexpectedly zeroAlthough it s possible to have a math error or misunderstanding result in this symptom, it smuch more likely that you believe that the variable has been assigned when it actually hasn tbeen. Causes of unbound variablesPHP automatically converts the types of variables depending on the context in which theyare used, and this is also true of unbound variables. In general, unbound variables are inter- preted as 0in a numerical context, in a string context, FALSEin a Boolean context, and asan empty array in an array context. The following code shows the effect of forgetting to bindtwo variables ($two_stringand $three); the resulting display appears in Figure 11-8: ); print( $one_string is equal to $one
); print( $two_string is equal to $two
); print( $three_string is equal to $three
); print( $one_string divided by $two_string is . ($one / $two) .
); print( $one_string divided by $three_string is . ($one / $three) .
); ?> #BREAK# 222Part IPHP: The BasicsFigure 11-8:The effect of unbound variablesCase problemsVariables in PHP are case sensitive, so the same name with different capitalization results in a different variable. Even after a value is assigned to the variable $Mississippi, the variable$mississippiwill still be unbound. (Capitalization aside, variables that are this difficult tospell are probably to be avoided for the same reason.) Scoping problemsAs long as no function definitions are involved, PHP variable scoping is simple: Assign a vari- able, and its value will be there for you from that point on in that script s execution (until thevariable is reassigned). However, the only variables that are available inside a function bodyare the function s formal parameters and variables that have been declared to be global ifyou have a puzzling, unbound variable inside a function, this is probably something you veforgotten. In the following code, for example, the variable $serial_nois neither passed in tothe function nor declared to be global: $name = Bond, James Bond ; $rank = Spy ; $serial_no = 007 ; function Answer($name) { global $rank; print( Name: $name; Rank: $rank; serial no: $serial_no
); } Answer($name); The resulting browser output looks like: Name: Bond, James Bond, Rank: Spy, serial no: because the variable is unbound inside the function. #BREAK# 223Chapter 11Basic PHP GotchasOverwritten VariablesPHP will allow you to use variables that have never had values assigned, but it also allowsvariable values to be changed and does not typically warn you when this is happening. Symptom: The variable has a valid value, just not the one you expectedSay you echo out a variable for debugging, and the result turns out to be something totallydifferent from the one you expected. This is almost certainly caused by PHP s free and easyway of assigning new values to variable names, exacerbated by poor coding style. There aretwo types of issues: global scope and local scope. Global overwritten variablesIf you use register_globals, and especially if you also rely on short variable names, youcan sometimes run into a situation where a global you weren t expecting pops out ofnowhere and overwrites a variable. For instance, you might have a form with a field named username , which on submit will create a variable called $_POST[ username ] which, if you use register_globals, will be automagically copied to a global variable called$username. But let s say you then initiate a session to get some unrelated session variable, in the course of which you have inadvertently also set $_SESSION[ username ] which, guess what, is also copied to a global called $username. Depending on how the variables_ ordersetting in your php.inifile is set, the session value may overwrite the form value. Ifyou re maintaining a large and complex PHP system, this kind of error can be a nightmaretotrack. Variable Naming ConventionsOne way to avoid a lot of the gotchas in PHP is to decide on, and to rigorously use, a set of vari- able naming conventions for all of your code. In the frequent cases where variables will beassigned and used in widely separated places in the same script and even across scripts, such aset of standards will save lots of time referring back and forth. What conventions you decide onare less important than that you have some standard in the first place. That said, here are a fewtips to help you decide what to do: .A common mistake many new programmers make is that variables must somehow be an abbreviation of the thing they represent. Remember, a variable is not an abbreviation; but rather a stand-in for some value that may change depending on circumstances or asa script executes. A longer, meaningful and easy to remember variable name is betterthan a shorter variable name that is anybody s guess. .Variable names that consist of multiple words strung together can be made morereadable by using underscores (for example, $office_address) or initial capitalization($OfficeAddress). There is some sense to the notion that the underscore solution cancreate confusion with function naming conventions. Use what works best for you. .In a more general sense, remember that you may not be the only person that has to readthis code. You may get really excited about PHP and get involved in one of the manyopen source projects that use PHP. You may even start your own project (we d bedelighted to see that happen)! In either case, readable code will be a must; and goodvariable names are a foundation of producing readable code. #BREAK# 224Part IPHP: The BasicsThe answer, of course, is to eschew register_globalsand use better variable names. It canreally help to have a good text editor with autocomplete, a useful feature if you are motivatedby wanting to type fewer characters. Local overwritten variablesSimilar issues can happen within a single script, but should be easier to track. Say you assigna value to a variable, and then 2,000 lines of code later you use the same variable name bymistake you ve just erased your first variable assignment. This is especially easy to dowhen you re including big library files from all over the place. Solution? If you suspect this is happening, you need to do a search through your PHP file andall included files for the questionable variable name. Don t forget to check the included files! Just hope you catch these things early, because they re a big pain if allowed to grow. Function ProblemsMany problems having to do with function calls result in fatal errors, which means that PHPgives up on processing the rest of the script. Symptom: Call to undefined function my_function() PHP is trying to call the function my_function(), which has not been defined. This could bebecause you misspelled the name of a function (built-in or user-defined) or because you havesimply omitted the function definition. If you use include/requirefiles to load user-definedfunctions, make sure that you are loading the appropriate files. If the problem involves a fairly specialized, built-in function (for instance, it is related to XMLor arbitrary-precision math), it may be that you did not enable the relevant function familywhen you included PHP. If, for example, all the BC functions seem to be undefined, they prob- ably were not included at compile time. To fix this (under Unix systems), you would need toreconfigure PHP and recompile. Under Windows systems, you just need to make sure that thecorrect .dllfiles are loaded in php.ini. Symptom: Call to undefined function () In this case, PHP is trying to call a function and doesn t even know the function s name. This is invariably because you have code of the form $my_function(), where the name ofthe function is itself a variable. Unless you are intentionally trying to exploit the variable- function-name feature of PHP, you probably accidentally put a $in front of a sensible call tomy_function(). Because $my_functionis an unbound variable, PHP interprets it as theempty string which is not the name of a defined function and gives this uninformativeerror message. Symptom: Call to undefined function array() This problem has a cause that is similar to the cause of the previous problem, although it still baffled us completely the first time we ran into it. It can arise when you have code likethe following: $my_amendments = array(); $my_amendments(5) = the fifth ; #BREAK# 225Chapter 11Basic PHP GotchasUnless you look closely, this looks like an innocent pair of statements to create an array andthen store something in that array, with the number 5as a key. And yet PHP is telling us thatarray()is an unbound function, even though we know that it is a very standard built-in func- tion. What s going on? The fault is actually with Line 2 above, rather than with Line 1. If we want to access an ele- ment of $my_amendments, the correct syntax is $my_amendments[5], with square brackets. Instead, we used parentheses, which the parser interprets as an attempted function call. Ittakes what is immediately before the left parenthesis to be a function. Instead, what comesbefore the parenthesis is an array, which is not a function; PHP gives up on us, with thisobscure complaint. Symptom: Cannot redeclare my_function() This is a simple one somewhere in your code you have two definitions of my_function(), which PHP will not stand for. Make sure that you are not using includeto pull in the samefile of function definitions more than once. Use include_onceor require_onceto avoid seeing this error, with the caveat that, well, you won t see this error. Why might that be bad? It s conceivable that you could define two distinctly different functions and inadvertently givethem the same name. This runs the risk of exposing your mistake at a somewhat inconvenientmoment. Symptom: Wrong parameter countThe function named in the error message is being called with either fewer or more argumentsthan it is supposed to handle. Math ProblemsThe problems that follow are specific to math and the numerical data types. Symptom: Division-by-zero warningSomewhere in your code, you have a division operator where the denominator is zero. Themost common cause of this is an unbound variable, as in: $numerator = 5; $ratio = $numerator / $denominator; where $denominatoris unbound. It s also possible, of course, that the legitimate result of a computation is producing a zero denominator. In this case, the only thing to do is catch itwith a test and do something reasonable if the test applies. See the following example: $numerator = 5; if ($denominator != 0) $ratio = $numerator / $denominator; elseprint( I m sorry, Dave, I cannot do that
); #BREAK# 226Part IPHP: The BasicsSymptom: Unexpected arithmetic resultSometimes things just don t add up (or multiply up, or subtract up). If you are having thisexperience, check any complex arithmetic expressions for unbound variables (which wouldact as zeros) and for precedence confusions. If you have any doubt about the precedence ofoperators, add (possibly redundant) parentheses to make sure the grouping is as you intend. Symptom: NaN (or NAN) If you ever see this dreaded acronym, it means that some mathematical function you usedhas gone out of range or given up on its inputs. The value NANstands for Not a Number, andit has some special properties. Here s what happens if we try to take the arccosine of 45, eventhough arccosine is defined only when applied to numbers between 1.0 and 1.0: $value = acos(45); print( acos result is $value
); print( The type is . gettype($value) .
); $value_2 = $value + 5; print( Derived result is $value
); print( The type is . gettype($value_2) .
); if ($value == $value) print( At least that much makes sense
); elseprint( Hey, value isn t even equal to itself!
); The browser output looks like: acos result is NANThe type is doubleDerived result is NANThe type is doubleHey, value isn t even equal to itself! Oddly enough, NANisa number, at least in the sense that its PHP type in this example turnsout to be double rather than string. It also infects other values with not-a-numberness whenused in math expressions. (This behavior is a feature, not a bug, when used in very complexcalculations that must be correct. It s better to have the whole value be tagged as untrust- worthy than have one subexpression be silently bogus.) Finally, any equality comparison thatinvolves NANwill be false NANis neither less than, nor greater than, nor equal to any othernumber, including itself. It is always unequal (!=) to all numbers, including itself. (The NANvalue is not a PHP-specific feature it is part of the IEEE standard for floating-point arith- metic, which is implemented by the C functions that underlie PHP.) Because of the contagion of NANvalues, this kind of problem can be difficult to debug. Thebest way to try to find the original offending NANis with diagnostic printstatements, espe- cially because comparison tests will give counterintuitive results. You can explicitly test forNANvalues using the built-in is_nan()function, implemented in PHP4.2.0, which returnsTRUEif the number submitted is not a number or FALSEotherwise. In earlier versions (youaren t using an earlier version, are you?), you can cobble together your own function for NANtesting like so: function is_nan($value) { return($value != $value); } It uses the weird comparison properties of NANas a type checker. #BREAK# 227Chapter 11Basic PHP GotchasTime-outsOf course any download can occasionally time out before a complete page can be delivered. However, this shouldn t be happening frequently on your local development server! If it does, you may have an issue that has nothing to do with slow Internet channels or server overload. The most interesting reason for a time-out is an infinite loop. These can be difficult to trackdown quickly, as in this example: //compute the factorial of 10$Fact = 1; for ($Index = 1; $Index <= 10; $index++) $Fact *= $Index; This code shows a nasty little collaboration between a loop and a case confusion the lower- case $indexthat is incremented has nothing to do with the $Indexthat is being tested, sothe test will never become false. Also see the discussion of infinite recursion in the Document contains no data section earlierin this chapter. Whether due to a loop or a recursive loop, any PHP code that runs forever is not going to give you a happy result. The exact symptom you see is likely to depend onwhether PHP runs out of memory first (and crashes, returning no HTML) or runs out of timefirst (and your browser informs you of a time-out). SummaryIn Table 11-1, we summarize the gotchas in this chapter by mapping symptoms to possiblecauses. We also offer some suggestions on how to fix the most common problems. Table 11-1: From Symptoms to CausesSymptomPossible CausesAdvice(New installation) Text of file The PHP engine is not being Make sure that your request displayed in browser windowinvoked, possibly because you are is to the Web server, either opening it via the local filesystem via localhost (http:// rather than as a request to your localhost/[path]) if testingserver.on the server machine, or by thefull URL (www.site.com/ [path]). (New installation) PHP blocks PHP is not being invoked Check your Web server showing up as text under HTTP, properly. Your Web server may configuration, and the PHP init or browser prompts you to not be set up to map the right file (php.ini). save file or visit an external suffixes (for example, .php) to file repositorythe PHP engine, or there may be a problem with the location or contents of php.ini. Continued13 #BREAK# 228Part IPHP: The BasicsTable 11-1(continued) SymptomPossible CausesAdvice(New installation) Server or Often due to Internet/DNS/Try loading a pure HTML file host not found/Page cannot Web-server configuration with a suffix you have not set be displayedproblems, rather than PHP.up for PHP (for example, .html) tzo rule out PHP problems. Trygetting to the same file using anIP address rather than a domainname if that works, theproblem is DNS-related. Totally blank pageUsually due to malformed View the HTML source from a HTML (whether produced by browser. Look especially for PHP or not).unclosed , , or

Password:

Date:

Entry:

This file is the form handler mentioned in the ACTIONattribute of the preceding form, namedform_check.php. As you can see from this example, this script will not insert anything into the database if avalid username and password are not provided or the user isn t logged into the localhost. You will also get an e-mail message warning you of a possible breach attempt. Learn to make backupsAnd finally, the biggest part of database security may be backing up. Take an hour to learnthe best way to back up data in your particular database (for example, via themysqldumpcommand in MySQL), and then schedule regular backups right away. Even better, with a littleforesight you can also set up an automatic database backup schedule. SummarySQL is not rocket science. The four basic data-manipulation statements supported by essen- tially all SQL databases are SELECT, INSERT, UPDATE, and DELETE. SELECTgets data out of thedatabase, INSERTputs in a new entry, UPDATEedits pieces of the entry in place, and DELETEgets rid of an entry. Designing databases is where most of the difficulty lies. Not all Web developers will be askedto do this. The designer must think long and hard about the best way to represent each pieceof data and relationship for the intended use. Well-designed databases are a pleasure to pro- gram with, while poorly designed ones can leave you pulling your hair out while contemplat- ing numerous connections and icky joins. SQL databases are created by so-called data structure statements. The most important ofthese are CREATE, ALTER, and DROP. As one would expect, CREATE TABLEdefines a new tablewithin a database. ALTERchanges the structure of a table. DROPis the nuclear bomb of SQLcommands because it deletes entire tables or sometimes even whole databases. Good database design is also a security issue. By employing reasonable prophylactic measures, a SQL database can enhance the security of your site. The best defense against intrusion, ofcourse, is maintaining a strict backup schedule so every new SQL maintainer should learnthe most efficient way to make backups. … #BREAK# MySQL DatabaseAdministration MySQL is one of the easiest databases to administer on all plat- forms; and because it s so lightweight, it can run on even low- powered PCs. Thus, PHP developers have long found it convenient tothrow a copy of MySQL on client machines even on laptops for a complete local Web development environment. Many developerslearn to run their own MySQL installations so they can work at homeor on the road, using the OS of their choice. Work teams also some- times prefer developers to each use a separate local MySQL installa- tion, so that there is no single point of failure that could affect anentire development group. And many PHP-based Open Source pro- jects assume complete familiarity with MySQL database administra- tion for all developers. Unlike some other databases, it should be well within the capabilityof any PHP developer to self-administer a MySQL database. There area plethora of tools, both in MySQL itself and available from third par- ties, to make this job even easier. Many PHP-based application pack- ages, both commercial and Open Source, also require familiarity witha MySQL database to install, run, and debug the Web app. So even ifyou don t plan to write all your own PHP code yourself, getting com- fortable with MySQL administration will pay many dividends. MySQL LicensingBefore installing any piece of Open Source software, you shouldclearly understand all the associated licensing issues. This is espe- cially true of products like MySQL that have dual commercial andOpen Source licenses. Unfortunately, MySQL licensing at the time of this writing is in fluxand has caused momentary incompatibilities with PHP s license. Untilthis situation gets definitively ironed out, PHP developers need to beextra careful to ensure that they are in compliance. This goes doublefor anyone who distributes (rather than simply uses) MySQL in eithera commercial or open source context. For some of the later releases of PHP4, MySQL client libraries werebundled with PHP. In the summer of 2003, MySQL AB creators ofthe MySQL database decided to adopt the General Public License(GPL) for noncommercial use. In many ways, this is a simpler and1414CHAPTER …In This ChapterMySQL licensingInstalling MySQLAdministering MySQLPHPMyAdminBackupsReplicationRecovery …#BREAK# 260Part IIPHP and MySQLless restrictive licensing scheme than before, but it happened to be incompatible with PHP sApache-style license, and therefore the PHP Group no longer felt able to distribute the databaselibraries. At press time this issue was in the process of being amicably worked out to allowOpen Source combined works to be distributed without charge but you should still checkto be absolutely positive that this is the case. The definitive location for MySQL licensinginformation is www.mysql.com/doc/en/Licensing_and_Support.html. There are separate sections for commercial use and for use under the GPL which is probablythe use most relevant to most PHP developers. You should read the license carefully, but here are some common use cases that will beaffected by MySQL s new licensing scheme: .Web site only:If you use MySQL solely as part of a commercial or noncommercial Website, you may use it without worrying about licensing issues. MySQL AB suggests youpurchase a support contract to further development work. .Open Source project under GPL:If you distribute MySQL server or client libraries aspart of an Open Source project properly released under the GNU General Public License, you can (and must) redistribute MySQL and its source code freely. .Commercial redistribution:If you bundle MySQL server or client libraries as part of acommercial product, you must purchase a commercial license from MySQL AB. .Open Source project under non-GPL license:This is the potentially problematic situa- tion at the moment, because if you bundle MySQL server or client libraries with yournon-GPLed Open Source project, your code could be infected by the GPL. Check theMySQL licensing page to determine the most current status of MySQL in relation toyour project. Remember that in many cases you can evade these licensing strictures simply by not redis- tributing the database yourself but rather requiring your users to procure and install MySQLseparately. Installing MySQL: Moving to Version 4Remember to install the MySQL server and client libraries beforeinstalling PHP! Although it snot strictly necessary in every circumstance, especially on Windows, it s always a good habitwith PHP to make sure that all third-party servers and libraries are properly installed beforetelling PHP to link to them. Preinstall considerationsMySQL was in version 3 for a long time, and many PHP developers got used to working with it during this period. However, MySQL 4 has introduced some innovations and changes, bothon the database side and the PHP side. Both new and experienced webdevs should take thetime to familiarize themselves with these changes. Even if you have a lot of experience withMySQL 3, you shouldn t necessarily expect to be able to install and use MySQL 4 with exactlythe same procedures. There are three main MySQL-specific issues to consider before you install new versions ofMySQL and PHP: incompatible new client libraries, the new PHP mysqli extension, and newtable types that support transactionality. #BREAK# 261Chapter 14MySQL Database AdministrationTransactionalitybasically means the ability to treat a group of database operations as a singleunit for the purposes of accepting or rejecting the data. So for instance, an e-commerce trans- action might have several steps touching numerous different tables registering you as a newuser, collecting your payment and shipping information, debiting the product from inventory, and so forth but you don t want any of these changes to be made unless the credit cardcharge goes through successfully, even though that step comes at the end of the purchasingprocess. In this case, you need a transactional database that will keep track of changesthroughout the process and either commit them all as a unit or roll them all back as a unit. Until version 4, MySQL was not a transactional database, but now it supports transactionality. New client librariesThe client libraries for MySQL 3 and 4.0 are forwards-incompatible with the MySQL serverfrom version 4.1 and up. Therefore, if you want to use MySQL 4.1+, you will have to rebuildPHP with the new libraries. The reverse is not true: MySQL 4.1 client libraries can still beused with older versions of MySQL server. You may also have to update your permissionstable and any columns containing password hashes calculated by MySQL that you have created in any tables. The main difference between the client libraries has to do with authentication. The MySQLPASSWORD()function used to result in a 16-byte hash. From version 4.1.1 onward, it nowresults in a 41-byte hash. Since MySQL uses the PASSWORD()function to set its own user permissions schemes, you will need to update pre-4.1.1 MySQL grant tables using themysql_fix_privilege_tablesscript. You will also potentially need to alter by hand anycolumns in other tables that take input from MySQL s PASSWORD()function, making them 41-bytes long. The actual contents of these columns do not need to change MySQL 4.1 will continue to accept hashes shorter than 41 bytes but the column sizes need to beincreased to accommodate new values. mysqliThe iin mysqli stands for improved. The new mysqli extension to PHP was designed to let youaccess the new functionality of MySQL 4.1 and above especially transactionality, which isthe biggest new feature of MySQL 4. The mysql extension works only with versions of MySQL below 4.1; the mysqli extensionworks only with versions 4.1 and above of MySQL. Unfortunately, this extension is not easily compatible with the old mysql extension and itsassociated functions. Therefore, it s best to choose one or the other at compile time. At thetime of this writing, the mysqli extension is considered experimental and should probably notbe used in production. It s theoretically possible to compile PHP with both mysql and mysqli extensions if forinstance you want to use both a 3.x and a 4.1+ version of MySQL on the same machine butyou ll have to be very careful to avoid conflicts between client libraries. In practice, it s betterto simply choose one or the other. Comp Svcs: Note two successive hyphens in the following paragraph. Do not change to anem dash. If you choose to try mysqli, remember to disable the mysql extension, which is usuallyenabled by default. (In Unix builds, use the without-mysqlflag; in Windows, comment outthe mysql.dllextension in php.ini.) CautionCautionNote17 #BREAK# 262Part IIPHP and MySQLTransaction-safe tablesFor most of its existence, MySQL used tables of a proprietary type called MyISAM. Late in the3.xx release cycle, it introduced two new types, ISAM and heap, but they have not becomehugely popular. To this day, MyISAM is the default and by far the most common type. However, to support transactions in MySQL 4.1+ the MySQL team created two new types oftransaction-safe tables: InnoDB and BDB. If you want to use commits and rollbacks, you mustcompile MySQL with the ability to recognize one of these types and define each table asInnoDB. You can mix different table types in the same database, and also convert a MyISAMtable to an InnoDB table. You might also consider mixing types by using InnoDB tables on a master database that accepts writes, while sticking with MyISAM for slave databases thatprovide only reads. Think hard about whether you really need transaction-safe tables. They impose quite a bit ofextra overhead and are thus slower, take up more room on disk, and require different toolsand procedures. Some things, such as recovering from database corruption, are consider- ably different and possibly harder (although also potentially less common) if you re usingtransaction-safe tables. On the other hand, if you wish to use MySQL in enterprise situationswith transactions, row-level locking, foreign keys, and hot backup, you ll want to research theInnoDB alternative. The other type of transaction-safe table, BDB, is based on Sleepycat Software s BerkeleyDBstorage engine. BDB does not offer some of the other features of InnoDB, such as foreign keysand row-level locking, and it s a bit unclear which company will provide support for this setup. Because transaction-safe tables are still so uncommon, and presumably used mostly in situa- tions where resources are available for specialized database administrators and tools, the bulkof this chapter will concentrate on MyISAM tables. For more information on InnoDB tables, refer to www.innodb.comor www.mysql.com/doc/en/InnoDB.html. The Windows binary version of the MySQL server is built with InnoDB enabled by default. However, your tables will not actually be of the InnoDB type unless you define them to be. Downloading MySQLAll downloads for MySQL are located at www.mysql.com/downloads/index.html. Pick theversion number you want and, as exactly as possible, the platform you want. One peculiarity of MySQL is that, unlike most other Open Source servers, the producers pre- fer installation from binary rather than source. There may be situations where you have tobuild yourself, but in general it should be avoided if at all possible. MySQL is now sometimes distributed in Linux distros or as part of other packages; for thefreshest builds, however, it s better to uninstall these versions using whatever tools are pro- vided by your platform and then reinstall a new version. Installing MySQL on WindowsDefault installation on any version of Windows is now much easier than it used to be, as MySQLnow comes neatly packaged with an installer. Simply download the installer package, unzip itanywhere, and run setup.exe. This will walk you through the trivial process and by defaultwill install everything under C:mysql, which is probably as good a place as any. Tip17 #BREAK# 263Chapter 14MySQL Database AdministrationTest the server by firing it up from the command prompt the first time. Go to the location ofthe mysqld server, which is probably C:mysqlbin, and type: mysqld –consoleIf all went well, you will see some messages about startup and InnoDB. If not, you may have apermissions issue. Make sure that the directory that holds your data is accessible to whateveruser (probably mysql) the database processes run under. However, despite the nifty new install, MySQL AB has not gone all the way with the WindowsUI paradigm. The preferred way to run the MySQL server, client, and tools is still from thecommand prompt. MySQL will not add itself to the start menu, and there is no particularlynice GUI way to stop the server either. Therefore, if you tend to start the server by double- clicking the mysqldexecutable, you should remember to halt the process by hand (usingmysqladmin, Task List, Task Manager, or other Windows-specific means) before you shutdown the computer. Another rather odd way in which Windows users have it much harder than Unix users is thatthe MySQL manual currently comes distributed in one huge HTML or text file for Windowsusers, both in the Windows build and for download as a zip file. This file is so big that youmay find it unusable if your Windows machine is not new and fast. If possible, grab the tarballversion with one HTML file per chapter. You can extract it on a Unix machine and then copythe files over to your Windows box. Or you can always use the online documentation if youhave reliable Internet access. The Windows version of PHP comes with MySQL enabled by default, so you should now begood to go (modulo user management stuff, which we will describe in a later section). If youwish to turn off the mysql extension in favor of the mysqli extension, you need to commentout the mysql line and uncomment the mysqli line in the modules section of php.ini. Installing MySQL on UnixIf possible, use one of the binary versions of MySQL, preferably one with an installer. Onsome platforms (notably Linux), you will need to download the server and clients separately; on others, they are conveniently bundled. There is now a good selection of binaries, so it willnot be necessary for most people to build MySQL by hand. Some packages are distributed bythird parties, such as Debian, rather than by MySQL AB. Look around your usual source forbinary packages specifically built for your platform if you don t see a binary build onmysql.com. There can be very wide variation in where MySQL programs and data files are located, basedon precisely which package you re using and where you got it. The mysql.com manual containsa section on installation layouts, but it s often inapplicable or inaccurate. The most commonlocations are /usr,/usr/local, and /var. If you have to use a generic binary instead of a cushy installer-based version, installation willrequire a few extra steps. Type the following lines at the prompt to create a new mysqluserand install MySQL to run as that user (you ll have to be the root user): groupadd mysqluseradd -g mysql mysqlcd /usr/localgunzip < /path/to/mysql-VERSION-OS.tar.gz | tar xvf - ln -s full-path-to-mysql-VERSION-OS mysql17 #BREAK# 264Part IIPHP and MySQLcd mysqlscripts/mysql_install_dbchown -R root . chown -R mysql datachgrp -R mysql . bin/mysqld_safe --user=mysql & Users of MySQL 3.x should note that the new startup script for MySQL is now calledmysqld_saferather than safe_mysqld. However, the latter will still exist as a symbolic linkduring some transition period for backward compatibility. Now you are ready to build PHP with the MySQL client libraries. Use the --with-mysql=/ path/to/mysqlflag for older versions of MySQL or the --without-mysql--with-mysqli=/ path/to/mysql_configflags for 4.1+ versions of MySQL. Note that in MySQL 4, you shouldlink to the actual location of mysql_configrather than just to the MySQL directory. Themysql_configscript is a tool that helps provide information about compiling MySQL clients, such as library location. Installing MySQL on Mac OS XMySQL AB now maintains an OS X specific binary installer distribution that delivers a diskimage rather than a tarball. Simply download the .dmgfile, and double-click the resultingicon. The installer will walk you through the process, and suggest a default installation path. Mac Internet Explorer users may find that the MySQL file downloads under the name download.phprather than as mysql-standard-4.x.x.dmg. In this case, simply allow thedownload to complete and then change the name of the file. Post-installation housekeepingMySQL ships with a blank password for the root MySQL user. As soon as you have success- fully installed the database and clients preferably even before you build PHP with MySQLsupport you need to set a root password: mysqladmin -u root password new_password ; Obviously, you will replace the preceding word new_passwordwith an actual password. Under no circumstances whatsoever should you even thinkabout using your server machine sroot user s password as the root password here! The server root user and the database rootuser have no relationship to each other. Also, don t use your normal user password as thedatabase root password. Come on, don t be lame make up a fresh password. Unix users will also want to put your MySQL directory in your PATH, so you won t have tokeep typing out the full path every time you want to use the command-line client. For bash, it would be something like: export PATH=$PATH:/usr/local/mysqlAdjust this to suit your own shell. If you add an entry for this location to the PATHline in yourshell s startup file (for example, .bashrc), you won t have to do this step every time you log into the machine. Your MySQL server is now ready to use. Caution17 #BREAK# 265Chapter 14MySQL Database AdministrationBasic MySQL client commandsIt may surprise you to know that the binary named mysqlin your mysql/bindirectory is notthe server, but the client (the server is mysqld). When you type mysqlinto a shell, you areusing the MySQL command-line client to access some MySQL server. To connect to the MySQL server using the command-line client, the basic command is: mysql [-h hostname] [-P portnumber] -u username -pYou almost certainly need to pass the username; if you don t, the client will try the name ofyour shell user. If you don t pass the password flag, mysql will check whether a password isneeded for the user you claim to be and if so, it will reject you. If you re connecting to alocal host, you don t need the hostname flag; if you re connecting to the default port (3306), you don t need the port number flag. There are a bunch of other options, but usually this isall you need the first time. Assuming you use the username root, you will be prompted forthe root password that you just set in the previous step. At this point, you will need to select a database to use. The command for that is: USE databasename; The semicolon is optional for this command, but you need one for every other SQL commandso you might as well get used to using it. Until you create new databases, there are only twodatabases in a fresh install: mysqland test. If you just connected to MySQL as the root user, you have access to both; if you are connected as any other user, you have access only to test. The command SHOW TABLES;will dump a list of all the tables in this database. To quickly see the structure of a database table, use SHOWCOLUMNSFROMtablename;. Thisdisplays all the columns with their types, sizes, default values, and other helpful information. To see all the values in a table, just do a SELECTwith unrestrictive conditions: SELECT * FROM tablename; Be careful though, since in live databases this kind of query can be huge and take up a lot ofresources. If you have reason to suspect that the data set is more than a few rows, you shouldtake steps to limit the query. See Chapter 13 for more information on how to write SQL statements like SELECT, INSERT, and so forth. Remember that one of the best ways of debugging problems with SQL state- ments in your PHP code is to try them out (with suitable fake data plugged into the vari- ables) using the MySQL command-line client rather than the PHP client. See Chapter 19 formore information on debugging SQL in your PHP. Finally, to get out of the MySQL client session, use the command quit;. Again, the semicolonis optional for this command. This should drop you back into your normal shell. MySQL User AdministrationA big part of using MySQL safely and effectively is understanding its privilege system, andlearning how to use the tools provided for controlling user privileges. Cross- Reference17 #BREAK# 266Part IIPHP and MySQLMySQL allows you to grant quite fine-grained permissions to different users from differentclient locations. There are four descending levels of privileges: global, database, table, andcolumn. So in theory, you could allow a particular user to write data only to certain columnsof certain tables of certain databases on your MySQL server. Or you could just as easily giveany database user connecting from anywhere the same powers as the root database user(although this is totally not recommended). Of course, for security reasons it s generally a good rule of thumb to grant each user only theminimal permissions necessary to perform his or her function. But here s the tradeoff: themore fine-grained your permissions scheme, the slower each and every INSERT, SELECT, UPDATE, and DELETEwill be. This makes sense, because MySQL is checking more grant tablesfor more fine-grained permissions. Realistically, not everyone really needs to worry about theperformance hit but if you do, you ll have to make some tradeoffs between security andperformance. The heart of the MySQL permission system is a table that every database administratorshould become very familiar with: the usertable of the mysqldatabase (which, along with a database called test, ships with every installation of MySQL). Let s look at a simplified version of this table (apologies for the line-wraps, but that s how you ll see it in many shellwindows too). mysql> select * from user; +———–+——+———-+————-+————-+ ————-+————-+ | Host | User | Password | Select_priv | Insert_priv | Update_priv | Delete_priv | +———–+——+———-+————-+————-+ ————-+————-+ | localhost | root | | Y | Y | Y | Y | Y | Y | | dhcppc2 | root | | Y | Y | Y | Y | Y | Y | | localhost | | | N | N | N | N | N | N | | dhcppc2 | | | N | N | N | N | N | N | +———–+——+———-+————-+————-+ ————-+————-+ 4 rows in set (0.00 sec) As you can see, there are several specific global permissions, which are represented in thetable by a Yor an N. A Yin the user table stands for a global privilege affecting all tables in alldatabases on this MySQL server. If the MySQL server gets a request from a user who has an Nin the field corresponding to that action, it will start going down the hierarchy of privilegescope first to the dbtable for database-level privileges; then if it finds all Ns in that tabletoo, to the tables_privtable for table-level privileges; and finally to the columns_privtablefor column-level privileges. Only after exhaustively checking all the grant tables will it reportan authentication error to the client. If you grant column or table level privileges to even a single user among many, MySQL willcheck these grant tables for all users. Therefore, giving column or table privileges to even oneuser could significantly slow down all your SQL statements for all users. Caution17 #BREAK# 267Chapter 14MySQL Database AdministrationThere is no way to grant a user the ability to create or drop any table of a database withoutalso giving that user the ability to drop the database entirely. However, you can prevent theuser from creating or dropping other databases on the same server. You also cannot use theMySQL grant tables to block connections from certain IP addresses or hostnames. There are two different ways to add or edit user permissions in MySQL (assuming you re theroot database user): by direct SQL statements (for example, putting a Yby hand into everyrelevant field of every relevant grant table) or by use of the GRANTand REVOKEsyntax. Thelatter is easier, and less dangerous if you make a small mistake, since in most cases your querywill choke with a SQL error instead of just leaving a gaping security hole. To add a new MySQL user: GRANT priv_type [(column1, column2, column3)] ON database.[table] TO user@host IDENTIFIED BY new_password ; where columns and tables are optional and additional priv_typescan be appended in acomma-separated list. The types of privileges and their scope are shown in Table 14-1. Table 14-1: MySQL Privilege ScopePrivilegeGlobalDatabaseTableColumnALL.. ALTER… CREATE… CREATETEMPORARYTABLE… DELETE… DROP… EXECUTE.. FILE.. INDEX… INSERT…. LOCKTABLES.. PROCESS.. REFERENCES.. RELOAD.. REPLICATIONCLIENT. REPLICATIONSLAVE. SELECT…. ContinuedCaution17 #BREAK# 268Part IIPHP and MySQLTable 14-1(continued) PrivilegeGlobalDatabaseTableColumnSHOWDATABASES. SHUTDOWN. SUPER.. UPDATE…. USAGE.. GRANTOPTION… Obviously, there s no point in trying to give anyone the SHUTDOWNprivilege at the table level. You will merely get an error message telling you to RTFM. If you grant ALLto a column, table, or database, the user will get only the basket of privileges appropriate to that level. You should be extra-careful about giving users the following privileges, which are all dangerous: GRANT,ALTER, CREATE, DROP, FILE, SHUTDOWN, PROCESS. No normal database user, especiallya PHP user, should need these permissions in production. The syntax for revoking privileges is very similar, although simpler: REVOKE priv_type [(column1, column2, column3)] ON database[.table] FROM user@host; After you grant or revoke privileges to any user, you need to force the database to reload thenew privilege data into memory. You do this by issuing the FLUSHPRIVILEGEScommand. Youcould also start and stop the server, but that s impractical in many circumstances. This is all well and good, but by now you re probably thinking: But what actual permissionsshould I actually grant to my actual PHP user? Let s look at some common cases from the realworld. Local developmentFor purely local stuff, especially on a machine that isn t connected to the Internet all the timeor is tucked securely behind a good firewall, almost anything goes. If you need to experimentwith your schema, this is the place to do it so it s appropriate to have permissions likeALTER, CREATE, DELETE, and DROPin addition to the normal SELECT, INSERT, UPDATE. A lot ofpeople will find it convenient to just grant ALLPRIVILEGESon a certain database to a localuser, like this: GRANT ALL PRIVILEGES on database.* TO username@localhostIDENTIFIED BY password ; Standalone Web siteA self-hosted database probably needs to accept connections from numerous Web servers inthe same domain. In production, all machines should be limited to SELECT, INSERT, UPDATE, and possibly DELETE although many systems never actually delete data, and it s a littlesafer not to do so. Since there probably won t be multiple databases on a standalone Web17 #BREAK# 269Chapter 14MySQL Database Administrationsite s production database, global permissions are faster with not much more real securityrisk. So a possible grant statement might be: GRANT SELECT, INSERT, UPDATE ON *.* TO phpdbuser@%.example.comIDENTIFIED BY password ; However, this is the situation that is most likely to use master-slave replication. Often, theseMySQL clusters are configured so that all writes go to the master, while the slaves do nothingbut serve up very fast reads. In that case, you would give only SELECTprivileges on each slaveand only INSERTand UPDATEprivileges on the master possibly to two different databaseusers. Shared-hosting Web siteIf you are an ISP that offers shared hosting, or a customer hosting your Web site on one, yourprimary concern should be security over performance. Under no circumstances do you wantone user to be able to tamper with or delete data belonging to another user. Unless each user has her own MySQL instance running on her own port, the ISP administratorshould not allow users to create or drop globally. Obviously, though, there is no good way todeny table creates or drops, which (as we explained previously) implies that each user willalso be able to drop his own database if he so desires. Yes, that s right: If your users can definenew tables, as they almost certainly will have to in this situation, there s no good way to pre- vent them from blowing away all their data with a single command! That s part of the easy- come easy-go thrill of MySQL. The database administrator can and should, however, preventusers from being able to do this to other users on the same server. ISPs should not use wildcarding for usernames or hostnames. Each user account should beconnecting from one and only one server in your own domain space. There s no good reasonto accept MySQL client connections from outside the firewall your users should be com- fortable ssh-ing into their shared hosting space to tinker with their database, or they will wantto use a GUI tool. So a common grant for this situation might be: GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTERON database.* TO user@servername.example.comIDENTIFIED BY password ; One deplorable practice often used by shared hosts is allowing or even requiring a user toreuse his domain username and password for a MySQL username and password. This is low- rent and could cause untold problems: If your ISP allows this, we recommend you switch Webhosts now. PHPMyAdminAlthough we recommend that you familiarize yourself with the MySQL command-line clientand use it as much as possible, the truth is that many PHP users dislike the command lineand find it difficult to visualize their structured data when they see it in a tiny shell windowwith awful line wraps. Some Web hosts also do not grant full shell access and therefore allowusers to administer and view their databases only through a Web interface. So although itpresents many security problems, the demand for Web-based GUI front ends to MySQL wasoverwhelming. If you re going to use one of these clients PHPMyAdmin is the most popular, but there are several similar packages you should at least know how to use it in the leastharmful way. #BREAK# 270Part IIPHP and MySQLThe major problem with PHPMyAdmin is that it doesn t make MySQL s security issues anyeasier to understand and then it adds some security issues of its own. In addition to MySQL sprivilege system, discussed previously, there is a Web security layer to handle appropriately. Unfortunately, PHPMyAdmin doesn t necessarily make it easy to get good Web security. Say you have a Web hosting account with shell access on a computer at a data center some- where. In a well-run network, you would have to log in to that computer over an encryptedconnection like ssh. Then you would have to enter a separate database username and pass- word, and possibly a hostname and port number on another machine, to connect to the MySQLdatabase server which would only accept connections from you that originated on the spe- cific computer that you have a user account on. It s not a perfect system when it comes tosecurity, there are no perfect systems but it s pretty good. In contrast, PHPMyAdmin doesn t necessarily require even one username or password. Anyonewho knows about the existence of your PHPMyAdmin installation can try to access it using abrowser from anywhere and in the default configuration, they would get right in withoutever hitting an authorization subsystem. Furthermore, the pipe between your browser andthe Web server will not be encrypted unless you go to the trouble to configure your Webserver to accept only HTTPS connections, so all data will pass back and forth over an ordi- nary HTTP connection. So how do you get the convenience of PHPMyAdmin while minimizing the risks? First, assessyour needs. A lot of people turn out to use PHPMyAdmin only very occasionally, for instanceto set up an initial database schema. If that describes your needs, then you don t need toinstall PHPMyAdmin on your production server at all. Install it locally, tweak your schema toyour heart s content, and then dump a copy of your local database as SQL that can be appliedto your production server. It s very easy, much safer, and minimizes downtime on your pro- duction servers. See the Backups section later in this chapter for more information on mak- ing copies of database structure and data using mysqldump. The only drawback of this method is that it becomes quite difficult to make the occasionalsmall schema change on a database that already has data in it. However, in this case youcould consider disabling PHPMyAdmin when it s not in use. You would simply set the direc- tory and file permissions of the PHPMyAdmin directory to disallow connections from the Webserver user (that is, nobody). The next time you need to use PHPMyAdmin, you d only haveto take a second to reset the permissions so the Web server user could once again read thefiles. You d also want to use http or cookie auth when the Web client was enabled, however. Another security measure would be to use SSL encryption on your PHPMyAdmin directory. This will provide a solution to the problem of unencrypted sensitive data being passedbetween your Web server and browser. See the following Web sites for more information: .www.modssl.org(Apache httpd) .www.microsoft.com/technet/treeview/default.asp?url=/technet/ prodtechnol/windowsserver2003/proddocs/deployguide/iisdg_mea_nfmd. asp(IIS 6) The most common method of enhancing security is to use PHPMyAdmin s http or cookie authschemes. These require the creation of a special PHPMyAdmin user who can read the MySQLgrant tables, as well as your normal database administrator user. The http method works onlywith Apache. The cookie method encrypts your password before writing it to a cookie, workson Web servers other than Apache, and is the only method that allows for a complete logout. #BREAK# 271Chapter 14MySQL Database AdministrationTo use either http or cookie-based authorization, create the PHPMyAdmin user this way (afterunzipping or untarring the PHPMyAdmin package under your Web server s document root): GRANT USAGE ON mysql.* TO pmauser @ localhost IDENTIFIED BY pmapassword ; GRANT SELECT ( Host, User, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv) ON mysql.user TO pmauser @ localhost ; GRANT SELECT ON mysql.db TO pmauser @ localhost ; GRANT SELECT ON mysql.host TO pmauser @ localhost ; GRANT SELECT (Host, Db, User, Table_name, Table_priv, Column_priv) ON mysql.tables_priv TO pmauser @ localhost ; For both schemes, you also need to set the following fields in the PHPMyAdminconfig.inc.phpfile: $cfg[ PmaAbsoluteUri ] = http://localhost/phpMyAdmin ; $cfg[ Servers ][$i][ host ] = localhost ; $cfg[ Servers ][$i][ auth_type ] = http or cookie ; $cfg[ Servers ][$i][ user ] = pmauser ; For cookie-based auth, you also need to set the following field: $cfg[ blowfish_secret ] = Supersecret passphrase ; Now when you try to connect, you will either get an Apache basic-auth popup box or a Webform. Enter your database administrator s username and password don t use the rootdatabase user, please! and you will now have exactly the same MySQL privileges you wouldhave when using the MySQL command-line client. Finally, there is one PHPMyAdmin authentication method you should not use: the so-calledconfig method. This simply means you put your database administrator username and pass- word in the config.inc.phpfile, and then anyone with a browser will be able to see yourdatabase. The only circumstance in which you should even consider using this method is ona local machine that does not accept HTTP connections from the outside world. Now that you ve set up PHPMyAdmin, you have easy access to a wealth of information aboutyour MySQL databases. Once you select a database from the list available to you, you will seethe main database screen, which will resemble Figure 14-1. From here, GUI users will probably find it easy to navigate PHPMyAdmin. Unfortunately, PHPMyAdmin uses somewhat different terminology for operations than the MySQL client: structure rather than show columns , browse rather than select , and export ratherthan dump but a little bit of experimenting should make things clearer. You may have a few scares with PHPMyAdmin if you happen to be color-blind, because allthe dangerous operations are represented by red icons or links. Luckily, you will be asked toconfirm every drop or mass-delete operation before it happens, which will minimize acci- dents as long as you don t hit Enter at the wrong time. Caution17 #BREAK# 272Part IIPHP and MySQLFigure 14-1:PHPMyAdmin s main database screenBackupsDatabase backups can be made in two ways: by copying the data directory directly (eithermanually or by means of the mysqlhotcopyscript on Unix) or by using the mysqldumptool towrite out a SQL file that will replicate your database. The former is a little faster, but the latteris more flexible. With mysqldumpyou can choose to copy just the structure of the database, just the data, or both. The most basic usage of mysqldumpis: mysqldump -u username -p databasename > dumpfilename.sqlThis command will dump a text file that can be read into another database server, like this: mysql -u root -p databasename < dumpfilename.sqlInstead of directing the output of mysqldumpto a file, you can also pipe it directly to anotherserver, like so: mysqldump -u username -p databasename | mysql -h remote-host -u remoteuser -p -C databasenameHowever, this can be less secure in some cases, since you have to tell the remote host toaccept database-modifying connections from external clients. #BREAK# 273Chapter 14MySQL Database AdministrationThis basic command is fine as far as it goes meaning it will result in a nice SQL file contain- ing both the structure and data of the named database. But sometimes you will want some- thing more specific than that: maybe just the structure, or just the data, or all the databaseson that server, or just some tables from your chosen database. MySQL allows you to bothspecify different combinations of databases and/or tables and to add option flags to yourcommand. If you want to select specific tables to dump from your chosen database, just list them afterthe database name: mysqldump -u username -p databasename table1 table2> dumpfilename.sqlIf you want to dump some but not all databases on your server, use the –databasesflag andthen list the databases. However, in this case, you will not be able to specify tables. mysqldump -u username -p –databases database1 database2 > dumpfilename.sqlIf you want to dump all databases, use the –all-databasesflag: mysqldump -u username -p –all-databases > dumpfilename.sqlYou can specify any of these options before specifying the databases and tables. There aremany mysqldumpoptions, but Table 14-2 lists the most commonly used options. Table 14-2: mysqldump OptionsOptionExplanation–add-locksAdds table locking to SQL file for faster inserts on the targettable. See also –opt. –add-drop-tableWill overwrite each table definition. Be careful with this option, as you could delete data! If you don t use this option but atable of the same name already exists, you will get an error onthe target database. -a, –allAll options. Be careful! -c, –complete-insertUse more complete insert statements with column names, instead of simply reading in values. –helpDisplays help message with options. -l, –lock-tablesLocks tables on the source machine before the dump. -n, –no-create-dbWill not create databases of the specified names if they don t exist already. Default with the –databasesand –all-databasesoptions. -t, –no-create-infoWill not create tables of the specified names if they don t existalready. -d, –no-dataJust the structure of the specified database(s) or tables. Continued17 #BREAK# 274Part IIPHP and MySQLTable 14-2(continued) OptionExplanation–optEqual to –quick –add-drop-table –add-locks –extended-insert –lock-tables. Fastest possibledump. Make sure you want to drop existing tables if there s a conflict. -q, –quickNo buffering. -r, –result-file=filenameDump result to file. In DOS, creates Unix-style line breaks. -w, –where= condition Select results by the WHEREclause in single quotes. Because mysqldumpis so easy to use, you should have no excuse for not adhering to a regularbackup schedule. This is why cronjobs were invented! If your data changes relatively infre- quently, you might be able to get away with weekly or fortnightly backups; if you have a fairlyhigh-traffic site, you ll want to schedule one every night. Users of PHPMyAdmin have access to mysqldumpthrough the Export tab. However, PHPMyAdmin currently offers only the most common options for your data dump. If you needmore control over the format of your SQL file, you ll have to use mysqldumpas previouslydescribed instead. ReplicationMySQL replication is based on a one-way single-master, single-or-multiple-slave model. Themaster database will handle all writes meaning all INSERTs, UPDATEs, and DELETEs, as well asall schema changes. The slaves will periodically get these changes from the master and in themeantime will be available for highly optimized read-only data serving (meaning all SELECTs). The master does not know anything about slave databases. It simply makes its binary logsavailable, and the slaves do all the rest: scheduling updates, connecting to the master, gettingthe changes, applying the changes, and so on. Thus, slaves are aware of the identity of themaster, but masters are not aware of the identities of slaves. If the master database goes down for any reason, no replacement will be automatically elected. The entire system is likely to become unperformative, as the slaves spend many resourcestrying in vain to connect to the master for updates, while PHP tries to perform writes withoutsuccess. The database administrator will have to manually break the existing master-slaverelationships and designate a new master by hand. Luckily, if something goes wrong with themaster, there s no way the slaves will have gotten out of sync so modulo a database admin- istrator noticing the problem and being available to deal with it, changing to a new masterdatabase should be relatively quick. Because there have been many changes and upgrades to replication in recent versions ofMySQL, many recent versions are incompatible with other recent versions in a replicationsetup. If you want to try replication, we recommend you make sure all the database serversinvolved are using the same version of MySQL, and furthermore, that this version be 4.0.3+. If you are trying to replicate with disparate versions of MySQL between 3.23 and 4.0.3, thingsare very likely to not work properly. #BREAK# 275Chapter 14MySQL Database AdministrationIn a nutshell, the operations that must be performed to establish MySQL replication are these: 1.Grant permissions to slave user on master. 2.Take snapshot of master data; copy to slave machines. 3.Shut down MySQL servers. 4.Restart MySQL servers with correct server-ids. 5.Establish master-slave relationship from each slave. Now we ll explain the process in more detail. You will need to create an account on the master database for slaves to use, with the REPLICATESLAVEprivilege. You do not need to grant any other privileges to this account. GRANT REPLICATE SLAVE ON *.* TO replicant@ % IDENTIFIED BY replpwd ; Next, lock the master server and take a snapshot of its state immediately before the replica- tion. On the master server, log in to a MySQL client session as the root user and issue thecommands: FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; This will prevent any changes from being made to the database until you are ready to bringup the cluster. You may also (depending on whether this server has been run with binary logging) see some data about the location of the binary log file and offset. If so, write it down; if not, use the default values (empty string) and 4, respectively. Next, copy the master database structure and data. There are two ways to do this. The first is to simply copy the mysql/datadirectory into a tarball or zip file by using one of thesecommands or a GUI procedure: tar -cvf master_snapshot.tar data/ zip master_snapshot.zip data/ Alternatively, you can use mysqldumpto make a backup as described in the next section. Copythis snapshot file to each slave server. Now shut down all the master and slave servers. Quit out of any mysql client shell sessions, and issue the command: mysqladmin -u root -p shutdownon each server. The reason you are shutting the servers down is to give them unique server- idvalues. They will use these values to find each other when they establish the master-slaverelationship. This value is set in each server s my.cnf file and will be read in on startup. OnWindows, the my.cnffile is located in one of two places: C:my.cnfor C:[Windowsdirectory]my.ini. On Unix systems, the global my.cnffile is found in /etc/my.cnfandthe server-specific file (which is probably the one you want to use) is found in /path/to/ mysql/data/my.cnf. First, set the server-idon the master machine. Find or create a file called my.cnfin theproper location for your platform, and make sure it contains the lines: [mysqld] log-binserver-id=117 #BREAK# 276Part IIPHP and MySQLRestart the master server: bin/mysqld_safe –user=mysqlIn each slave server s my.cnffiles, you need only the server-id, not the log-binline. Themost important thing is that you are absolutely positive that all the server-id values in yourcluster are unique! If they are not, bad things will happen. So the first slave s my.cnffilewould contain this line: [mysqld] server-id=2The second slave would set server-id=3, and so forth. Now, before you bring up each slave server, you may need to do a little bit of housekeeping. If this MySQL server has been used as a slave before, you may want to delete the files data/ master.infoand data/relay-log.info. You may also want to delete the .errand .pidfiles in the datadirectory. Also, if you copied the master s data snapshot into a tarball or zipfile, now is the time to copy it to the slave with a command like one of these (from themysqldirectory): tar -xvf master_snapshot.tarunzip master_snapshot.zipIf you used mysqldumpinstead, you have to wait until the server is back up. Now bring up the slave: bin/mysqld_safe –user=mysql –skip-slave-start –log-warningsIf you took your master data snapshot with mysqldump, now is the time to apply the SQL fileto the slave: mysql -u root -p databasename < master_snapshot.sqlFinally, you will establish the master-slave relationship. Log in to a mysql shell and then enter the following commands, substituting the values you wrote down at the beginning ofthe process: CHANGE MASTER TOMASTER_HOST= masterhostname , MASTER_USER= replicant , MASTER_PASSWORD= replpwd , MASTER_LOG_FILE= , MASTER_LOG_POS=4; START SLAVE; If there are problems, they will appear in the slave machine s error log. RecoveryNormally, MySQL does not require much attention. MySQL servers have happily putteredaway for months if not years with minimal administration. However, bad things do happen to data: Hard disks melt down, hosting centers lose power suddenly, and human error is aconstant and awful probability. If you have insufficient memory for all the applications you re17 #BREAK# 277Chapter 14MySQL Database Administrationrunning on a server, or insufficient disk space on a partition, you may also get an error thatrequires a recovery process. It must be admitted that MySQL seems to have minor databasecorruption events with greater frequency than heavier-weight databases or perhaps it sjust easier for the administrator to notice these events. Luckily, MySQL is designed to make it amazingly easy to repair small flaws in your data andget back up quickly. Only once have we had to actually scrap an entire database after repeatedattempts at recovery, and that disaster was caused by a total hard disk failure, which is some- thing a developer can do nothing to plan for or recover gracefully from except make frequentbackups. MySQL has long shipped with a command-line tool called myisamchkfor checking and repair- ing tables. This was a fine script but it suffered from one flaw: It could be run effectively onlywhen the database was shut down. That s fine when you re actually recovering from a disaster, since you re unlikely to be able to start your database anyway, but it s a significant barrier totrying to head off problems by regularly checking your data tables. Luckily, there is now a newtool that can be used during operation mysqlcheck. You can continue to use myisamchkwhen the server is not running. Both these tools basically can do three things: check a MyISAM table for errors, repair prob- lems, and optimize the database. The syntax by which you use the scripts is different, however. myisamchkThe myisamchkutility is invoked like this: myisamchk [options] table_nameormyisamchk [options] /path/to/mysql/data/database/table.MYIYou can wildcard both database directories and table names with an asterisk, which is morecommon than specifying a table, since you usually don t know exactly which table is causingthe problems. Use the following commands to check all the tables of all the databases on aserver: myisamchk [options] /path/to/mysql/data/*/*.MYImyisamchk [options] /path/to/mysql/data/*/*.MYD.MYI extensions designate index files, and .MYD extensions designate data files you need tocheck both. With no option flags, myisamchkwill simply check the designated table. If you pass the -roption flag, myisamchkwill repair the designated tables. You can also check and repair anycorrupted tables in a single operation: myisamchk --silent --force --fast --update-state -Okey_buffer=64M -O sort_buffer=64M -O read_buffer=1 -Owrite_buffer=1M /path/to/mysql/data/*/*.MYIThe command myisamchk-rtablenamewill also optimize a table that has been fragmentedby deletes and updates. #BREAK# 278Part IIPHP and MySQLmysqlcheckThe new mysqlchecktool has several handy advantages over myisamchk. As previously men- tioned, it can be used while the server is running even while serving up queries. It workson databases rather than tables, using the same syntax as the mysqldumptool. And instead ofhaving to remember the meaning of a bunch of option flags, you can copy and rename theexecutable to get different behaviors. The mysqlchecktool is invoked in one of these ways: mysqlcheck [options] databasename table1 table2 table3mysqlcheck [options] --databases database1 database2mysqlcheck [options] --all-databasesTo repair, analyze, or optimize databases, you simply copy the mysqlcheckfile and changeits name to mysqlrepair, mysqlanalyze, or mysqloptimize and then invoke it the sameway. So, for instance, to repair all the databases on your server, you might give this command: mysqlrepair -u root -p --all-databasesMySQL AB recommends that you set up a regular schedule of data file checking via cronjob, plus run one of these utilities every time you start up your MySQL server. This should helpkeep your data written out compactly for fast reads, head off problems while they re still tiny, and minimize your chances of a database problem that is visible to your users. SummaryMySQL is one of the easiest databases to administer, and learning to do so will offer manybenefits to PHP developers. MySQL installations have become easier of late on many platforms, and there are GUI as well as command-line tools available to help you view the structure ofyour database, manage database users, and make backups. More advanced MySQL adminis- tration tasks include disaster recovery and replication both of which are probably as easyto accomplish on MySQL as they could possibly be made. However, even long-time MySQLusers should consider the impact of recent changes to the MySQL-PHP relationship: licensingissues, client-version incompatibility, the new mysqli extension, and transactions. ... #BREAK# PHP/MySQLFunctionsAfter you ve installed and set up your MySQL database, you canbegin to write PHP scripts that interact with it. Here we will tryto explain all the basic functions that enable you to pass data backand forth from Web site to database. Information related to creating a MySQL database is at the end ofthis chapter, because it is a more advanced skill that builds on thefundamental MySQL skills discussed in the earlier parts of thechapter. The development version of MySQL, the 4.1.x series, introduces sev- eral new features that require some rewriting of the existing MySQLsupport in PHP. This new extension to PHP is called MySQL Improved. It must be built into PHP at install time using the --with-mysqliconfiguration directive, and the functions it offers are exposed withthe mysqli_prefix rather than the older mysql_prefix. The produc- tion quality versions of both the MySQL 4.1 series and the new PHPextension for it are some way off yet, so our focus is on the currentsupport, which should cover the bulk of the existing MySQL/PHPinstallations. The functions are, for the most part, analogous. We will, however, point out the corresponding mysqlifunctions where appro- priate and highlight any differences so you ll have an idea what toexpect if and when you decide to make the switch. Connecting to MySQLThe basic command to initiate a MySQL connection ismysql_connect($hostname, $user, $password); if you re using variables, ormysql_connect( localhost , root , sesame ); if you re using literal strings. The password is optional, depending on whether this particulardatabase user requires one (it s a good idea). If not, just leave thatvariable off. You can also specify a port and socket for the server($hostname:port:socket), but unless you ve specifically chosen a nonstandard port and socket, there s little to gain by doing so. Note1515CHAPTER ...In This ChapterConnecting to MySQLMySQL queriesFetching dataMetadataUsing multipleconnectionsError checkingCreating MySQLdatabases with PHPMySQL functions ...#BREAK# 280Part IIPHP and MySQLThe corresponding mysqlifunction is mysqli_connect, which adds a fourth parameterallowing you to select a database in the same function you use to connect. The functionmysqli_select_dbexists, but you ll need it only if you want to use multiple databases onthe same connection. You do not need to establish a new connection each time you want to query the database inthe same script. You will need to run this function again, however, for each script that inter- acts with the database in some fashion. Next, you ll want to choose a database to work on: mysql_select_db($database); if you re using variables; ormysql_select_db( phpbook ); if you re using a literal string. You will sometimes see these two functions used with an @prepended, such as @mysql_ select_db($database). This symbol denotes silent mode, meaning the function will notreturn any message on failure, as a security precaution. You should have display_errorsset to offon production servers anyway. You must select a database each time you make a connection, which means at least once perpage or every time you change databases. Otherwise, you ll get a Database not selected error. Even if you ve created only one database per daemon, you must do this, because MySQL alsocomes with default databases (called mysql and test) you might not be taking into account. You may find it convenient to group all your connection information into a custom connectfunction and put it someplace where you can access it from all your scripts, such as the phpincludes directory, or in the case of a virtual server, a site-specific include file. This functionmight look like the following: // Connect to a single dbfunction qdbconn() { $dbUser = myuser ; $dbPass = mypassword ; $dbName = mydatabase ; $dbHost = myhost ; if (!($link=mysql_connect($dbHost, $dbUser, $dbPass))) { error_log(mysql_error(), 3, /tmp/phplog.err ); } if (!mysql_select_db($dbName, $link)) { error_log(mysql_error(), 3, /tmp/phplog.err ); } } If you like, you could extend this function by creating links (for example, $link1, $link2) tomultiple databases on the same server. This code also records a MySQL error message to thePHP error log. Now that you ve established a connection to a specific database, you re ready to make aquery. Tip18 #BREAK# 281Chapter 15PHP/MySQL FunctionsMaking MySQL QueriesA database query from PHP is basically a MySQL command wrapped up in a tiny PHP functioncalled mysql_query(). This is where you use the basic SQL workhorses of SELECT, INSERT, UPDATE, and DELETEthat we discussed in Chapter 13. The MySQL commands to CREATEorDROPa table (but not, thankfully, those to create or drop an entire database) can also be usedwith this PHP function if you do not wish to make your databases using the MySQL client. You could write a query in the simplest possible way, as follows: mysql_query( SELECT Surname FROM personal_info WHERE ID<10 ); PHP would dutifully try to execute it. However, there are very good reasons to split up thisand similar commands into two lines with extra variables, like this: $query = SELECT Surname FROM personal_info WHERE ID<10 ; $result = mysql_query($query); The main rationale is that the extra variable gives you a handle on an extremely valuablepiece of information. Every MySQL query gives you a receipt whether you succeed or not sort of like a cash machine when you try to withdraw money. If things go well, you hardly needor notice the receipt you can throw it away without a qualm. But if a problem occurs, thereceipt will give you a clue as to what might have gone wrong, similar to the Is the machinenot dispensing or is your account overdrawn? type of message that might be printed on yourATM receipt. Another advantage of assigning the query string to a variable is that you can more easily viewthe query if you run into an error. Of course, you would accomplish this by writing the variableout to an error log never by dumping it out to the browser in production! The function mysql_querytakes as arguments the query string (which should not have asemicolon within the double quotes) and optionally a link identifier. Unless you have multipleconnections, you don t need the link identifier. It returns a TRUE(nonzero) integer value if thequery was executed successfully even if no rows were affected.It returns a FALSEinteger ifthe query was illegal or not properly executed for some other reason. For purposes of this chapter, we ve left the link identifier off; however, if you need to use mul- tiple databases in your script, you can use code like the following: $query = SELECT Surname FROM personal_info WHERE ID<10 $result = mysql_query($query, $link_1); $query = SELECT * FROM orders WHERE date>20030702 $result = mysql_query($query, $link_2); As expected, the MySQL Improved analog for this function is mysqli_query. It is very similarto its counterpart, however the linkand queryparameters change places and a third param- eter allows you to specify a result flag indicating how PHP should handle the result. If your query was an INSERT, UPDATE, DELETE, CREATETABLE, or DROPTABLEand returnedTRUE, you can now use mysql_affected_rowsto see how many rows were changed by thequery. This function optionally takes a link identifier, only necessary if you are using multipleconnections. It does nottake the result handle as an argument! You call the function like this, without a result handle: $affected_rows = mysql_affected_rows(); #BREAK# 282Part IIPHP and MySQLIf your query was a SELECTstatement, you can use mysql_num_rows($result)to find outhow many rows were returned by a successful SELECT. The mysqli_affected_rowsand mysqli_num_rowsbehave exactly the same as theirmysql_counterparts. The mysql_num_rowsfunction can be useful in paginating large data sets returned by MySQLqueries. Fetching Data SetsOne thing that often seems to temporarily stymie new PHP users is the whole concept of fetch- ing data from PHP. It would be logical to assume the result of a query would be the desireddata, but that is not correct. As we discussed in the previous section, the result of a PHP queryis an integer representing the success or failure or identity of the query. What actually happens is that a mysql_query()command pulls the data out of the databaseand sends a receipt back to PHP reporting on the status of the operation. At this point, thedata exists in a purgatory that is immediately accessible from neither MySQL nor PHP youcan think of it as a staging area of sorts. The data is there, but it s waiting for the commandingofficer to give the order to deploy. It requires one of the mysql_fetchfunctions to make thedata fully available to PHP. The fetching functions are as follows: .mysql_fetch_row: Returns row as an enumerated array .mysql_fetch_object: Returns row as an object .mysql_fetch_array: Returns row as an associative array .mysql_result: Returns one cell of dataIn our humble opinion, the functions mysql_fetch_fieldand mysql_fetch_lengthsare misleadingly named. They both provide information aboutdatabase entries rather thanthe entry values themselves. For instance, one might expect a function named mysql_ fetch_fieldto be a quick way to fetch a single-field result set (the ID associated with aparticular username, for instance), but that is not the case at all. The actual purpose of thesefunctions is explained in Table 15-2 at the end of the chapter but for the moment, the pointis not to be misled into thinking these functions will return database values. The difference between the three main fetching functions is small. The most general one ismysql_fetch_row, which can be used something like this: $query = SELECT ID, LastName, FirstNameFROM users WHERE Status = 1 ; $result = mysql_query($query); while ($name_row = mysql_fetch_row($result)) { print( $name_row[0] $name_row[1] $name_row[2]
n ); } This code will output the specified rows from the database, each line containing one row orthe information associated with a unique ID (if any). CautionTip18 #BREAK# 283Chapter 15PHP/MySQL FunctionsIn an enumerated array, the integers in brackets are called field offsets.Remember that theyalways begin with the integer zero. If you start counting at 1, you will miss the value of yourfirst column. The function mysql_fetch_objectperforms much the same task, except the row is returnedas an object rather than an array. Obviously, this is helpful for those among the PHP brethrenwho utilize the object-oriented notation: $query = SELECT ID, LastName, FirstNameFROM users WHERE Status = 1 ; $result = mysql_query($query); while ($row = mysql_fetch_object($result)) { echo $row->ID, $row->LastName, $row->FirstName
n ; } The most useful fetching function, mysql_fetch_array, offers the choice of results as anassociative or an enumerated array or both, which is the default. This means you can referto outputs by database field name rather than number: $query = SELECT ID, LastName, FirstNameFROM users WHERE Status = 1 ; $result = mysql_query($query); while ($row = mysql_fetch_array($result)) { echo $row[ ID ], $row[ LastName ], $row[ FirstName ]
n ; } Remember that mysql_fetch_arraycan alsobe used exactly the same way asmysql_fetch_row with numerical identifiers rather than field names. By using this func- tion, you leave yourself the option. If you want to specify offset or field name rather thanmaking both available, you can do it like this: $offset_row = mysql_fetch_array($result, MYSQL_NUM); or$associative_row = mysql_fetch_array($result, MYSQL_ASSOC); It s also possible to use MYSQL_BOTHas the second value, but because that s the default, it sredundant. In early versions of PHP, mysql_fetch_rowwas considered to be significantly faster thanmysql_fetch_objectand mysql_fetch_array, but this is no longer an issue, as thespeed differences have become imperceptible. The PHP junta now recommends use ofmysql_fetch_arrayover mysql_fetch_rowbecause it offers increased functionality andchoice at little cost in terms of programming difficulty, performance loss, or maintainability. Last and least of the fetching functions is mysql_result(). You should only even considerusing this function in situations where you are positive you need only one piece of data to bereturned from MySQL. An example of its usage follows: $query = SELECT count(*) FROM personal_info ; $db_result = mysql_query($query); $datapoint = mysql_result($db_result, 0, 0); The mysql_resultfunction takes three arguments: result identifier, row identifier, and(optionally) field. Field can take the value of the field offset as above, or its name as in anassociative array ( Surname ), or its MySQL field-dot-table name ( personal_info.Surname ). Caution18 #BREAK# 284Part IIPHP and MySQLUse the offset if at all possible, as it is substantially faster than the other two. Even better, don t use this function with any frequency. A well-formed query will almost always return aspecific result more efficiently. You should never use mysql_result()to return information that is available to youthrough a predefined PHP-MySQL function. The classic no-no is inserting a row and thenselecting out its ID number (extra demerits if you select on MAX(ID)!). Wicked bad style use mysql_insert_id()instead. All of the PHP functions for fetching MySQL data have identical mysqlicounterparts. Theytake the same parameters and return comparable results. A special MySQL function can be used with any of the fetching functions to more specificallydesignate the row number desired. This is mysql_data_seek, which takes as arguments theresult identifier and a row number and moves the internal row pointer to that row of the dataset. The most common use of this function is to reiterate through a result set from the begin- ning by re-setting the row number to zero, similar to an array reset. This obviates anotherexpensive database call to get data you already have sitting around on the PHP side. Here san example of using mysql_data_seek(): nn ); $query = SELECT title, publisher FROM books ; $result = mysql_query($query); while ($book_row = mysql_fetch_array($result)) { echo( n ); } echo(
Titles
$book_row[0]

n ); echo( nn ); mysql_data_seek($result, 0); while ($book_row = mysql_fetch_array($result)) { echo( n ); } echo(
Publishers
$book_row[1]

n ); ?> Without using mysql_data_seek, the second usage of the result set would turn back no 0rows because it has already iterated through to the end of the dataset and the pointer staysthere until you explicitly move it. This handy function helps greatly when you are formattingdata in a way that does not place fields in columns and records in rows. Getting Data about DataYou only need four PHP functions to put data into or get data out of a preexisting MySQLdatabase: mysql_connect, mysql_select_db, mysql_query, and mysql_fetch_array. Mostof the rest of the functions in this section are about getting information about the data you putinto or took out of the database or about the construction of the database itself. PHP offersextensive built-in functions to help you learn the name of the table in which your data resides, the data type handled by a particular column, or the number of the row into which you justinserted data. With these functions, you can effectively work with a database about whichyou know very little. Caution18 #BREAK# 285Chapter 15PHP/MySQL FunctionsObviously, you don t want J. Random Cracker to be able to find out everything about thestructure and contents of your database for the asking. If you have scripts that use thesefunctions extensively for instance, some kind of Web database-administration tool youneed a higher level of security. Make sure only the root MySQL user can use these tools, andpreferably use forms to pass in a password every time, or use one of the methods to limitusage to certain IP addresses. The MySQL metadata functions fall into two major categories: .Functions that return information about the previous operation only. .Functions that return information about the database structure in general. A very commonly used example of the first type is mysql_insert_id(), which returns theautoincremented ID assigned to a row of data you just inserted. A commonly used example ofthe second type is mysql_field_type(), which reveals whether a particular database field sdata must be an integer, a varchar, text, or what have you. Observe however, that this functionis also deceptively named. Rather than returning the MySQL type, it returns the PHP data type. For example, an ENUM-type field will return string . Use mysql_field_flagsto return morespecialized field information. This should be apparent when you consider that it works on aresult rather than on an actual MySQL field. It would be useful to have a function that got thepossible values for an ENUM field; but there isn t a canned version at this point. Instead, use a describe table query and parse the result using PHP s regex functions. Most of the data-about-data functions are pretty self-explanatory. There are a couple of thingsto keep in mind when using them, though. First, most of these functions are only effective ifused in the proper combination don t try to use a mysql_affected_rowsafter a SELECTquery and then wonder what went wrong. Second, be careful about security with the functionsthat return information about your database structure. Knowing the name and structure ofeach table is very valuable to a cracker. And finally, be aware that some of these functions areshopping baskets full of simpler functions. If you need several pieces of information about aparticular result set or database, it could be faster to use mysql_fetch_fieldthan all themysql_fieldfunctions one after the other. All of the MySQL metadata functions are fairly easy to use. However, their efficacy is directlyrelated to intelligent database design rather than a mere marker of the PHP s strengths. Gooddatabase practices will make these functions useful over the long haul. The mysqli equivalentfunctions are perfect analogues in each of these cases. Multiple ConnectionsUnless you have a specific reason to require multiple connections, you only need to make onedatabase connection per PHP page. Even if you escape into HTML many times within the page, your connection is still good (assuming it was good in the first place). You do not want tomake multiple connections if you don t have to, because that is one of the most costly andtime-consuming parts of most database queries. Conversely, there s no easy way to keep your connection open from page to page becausePHP and MySQL would never know for sure when to close it after visitors wander off. Therefore, your connection is closed at the end of each script unless you use persistent connections. Caution18 #BREAK# 286Part IIPHP and MySQLThe main reason you would need to use different connections is if you re querying two ormore completely separate databases. The most common situation in which you might do thisis when you re using MySQL in a replicated situation. MySQL replication is accomplishedthrough a master-slave setup, where you typically get reads from a slave and make writes tothe master. To use multiple connections, you simply open connections to each database as needed, andmake sure to hang on to the right result sets. PHP will help you do this by utilizing the resultidentifiers discussed in the Making MySQL Queries section earlier in the chapter. You passthe identifiers along with each MySQL function as an optional argument. If you re completingall your queries on one connection before moving on to the next, you don t even need to dothis; PHP will automatically use the last link opened. In this example, we are using connections from three different databases on different servers: 0 && $widgetcount > 0) { $link3 = mysql_connect( host3 , I , seed ); mysql_select_db( salesdb , $link3); $query3 = INSERT INTO saleslog (ID, date, userID, sku) VALUES (NULL, $today , $array1[0] , $array2[0] ) ; $result3 = mysql_query($query3, $link3); $insertID = mysql_insert_id($link3); mysql_close($link3); if ($insertID >= 1) { print( Perfect entry ); } else { print( Danger, danger, Will Robinson! ); } } else { print( Not enough information ); } ?> #BREAK# 287Chapter 15PHP/MySQL FunctionsIn this example, we have deliberately kept the connections as discrete as possible for clarity ssake, even going to the trouble to close each link after we use it. Without the mysql_close() commands, we would be running multiple concurrent connections which you may want todo. There s nothing stopping you from doing so. Just remember to pass the link value carefullyfrom one function to the next, and you should be fine. Building in Error CheckingThis section could have been titled Die, die, die! because the main error-checking functionis actually called die(). There was something about that title that failed to reinforce the warm, hospitable learning environment we cherish, so we went with the more prosaic subheading. die()is not a MySQL-specific function the PHP manual lists it in Miscellaneous Functions. It simply terminates the script (or a delimited portion thereof) and returns a string of yourchoice. mysql_query( SELECT * FROM mutual_fundsWHERE code = $searchstring ) or die( Please check your query and try again. ); Notice the syntax: the word or(you could alternatively use ||, but that isn t as much fun assaying ordie) and only one semicolon per pair of alternatives. Until quite recently, MySQL via PHP returned very insecure and unenlightening (except tocrackers) error messages upon encountering a problem with a database query. die()wasoften used as a way to exert control over what the public would see on failure. Now that noerror messages are returned at all, die()may be even more necessary unless you wantyour visitors to be left wondering what happened. Other built-in means of error-checking are error messages. These are particularly helpful during the development and debugging phase, and they can be easily commented out in thefinal edit before going live on a production server. As mentioned, MySQL error messages nolonger appear by default. If you want them, you have to ask for them by using the functionsmysql_errno()(which returns a code number for each error type) or mysql_error() (which returns the text message). Then you can send them to a custom error log by using the error_log()function: if (!mysql_select_db($bad_db)) { print(mysql_error()); } There s more to database error-handling than judicious use of die(), however. Serversbecome unavailable, data sets get corrupted, and so forth. We ve been fairly liberal in settingup connections and executing queries; but ideally, every interaction with the database shouldbe nested inside a conditional that returns the desired result on success and a nice cleanerror page on failure. This is where die()drops the ball. Execution immediately stops for theentire script, leaving off, if nothing else, closing tags for your HTML page if they are defined inPHP. Additionally, there may be plenty more perfectly good scripting or html left to go on thepage code that is unaffected by a dropped database connection or a failed query. Finally, die()doesn t let you know anything went wrong. Do you really think your users will tell you? Probably not. It s much more realistic that they will leave your site in disgust and neverreturn. An example of good error checking is shown as follows. #BREAK# 288Part IIPHP and MySQLfunction printError($errorMesg) { printf( %s
n , $errorMesg); } function notify($errorMesg) { mail(webmaster@site.com, An Error has occurred atexample.com , $errorMesg) } if ($link = mysql_connect( host , user , pass ) { // Things to do if the connection is successful} else { printError( Sorry for the inconvenience; but we are unableto process your request at this time. Please check backlater ); notify( Problem connecting to database in $SCRIPT_NAME atline 12 on date( Y-m-D ) ); } Even better, if you really want to get your feet wet with PHP5 s new OOP features, try usingexceptions, which we covered briefly in Chapter 6 and which get a more complete treatmentin Chapter 31. Creating MySQL Databases with PHPYou can, if you wish, actually create your databases with PHP rather than using the MySQLclient tool. This practice has potential advantages you can use an attractive front end thatmay appeal to those who find the MySQL command-line client horribly plain or finicky touse counterbalanced by one big disadvantage, which is security. See Chapter 14 for more information on how to minimize security issues when definingMySQL databases with PHP. To create a database from PHP, the user of your scripts will need to have full CREATE/DROPprivileges on MySQL. That means anyone who can get hold of your scripts can potentiallyblow away all your databases and their contents with the greatest of ease. This is not such agreat idea from a security standpoint. Furthermore, most external Web hosts very sensiblywon t let you do it on their servers anyway. If you re even considering creating databases with PHP, do yourself a big favor and at leastdon t store the database username and password in a text file. Make yourself type yourdatabase username and password into a form and pass the variables to the inserting handlereach and every time you use this script. This is one case where keeping the variables in anincludefile outside your Web tree is not sufficient precaution. For those who like to live dangerously, the relevant functions are: .mysql_create_db(): Creates a database on the designated host, with name specifiedin arguments. .mysql_drop_db(): Deletes the specified database. .mysql_query(): Pass table definitions and drops in this function. Cross- Reference18 #BREAK# 289Chapter 15PHP/MySQL FunctionsA bare-bones database-generation script might look like this: There are also prefab tools like phpMyAdmin that do much of this for you in a pretty way(see Chapter 14 for information about using phpMyAdmin). You simply fill out a Web form, and the PHP script on the back end will create the database according to your specifications. In many cases, the tool will also enable you to perform other administrative tasks, such aschecking the sizes of your databases or backing them up. This is even less secure than doingit yourself (insofar as you probably won t check over every line of the code with an eye tosecurity), but apparently people do use these tools without incident. Several other GUI tools are available that are not database-specific but will probably workwith MySQL. As MySQL has become more and more popular, a number of applications forboth Windows and Linux have come into play that allow you to administer MySQL databasesin the graphical fashion you may have become accustomed to. Like their Web counterparts, these applications offer full administrative control, but without the headache of exposingyourself to the security risk of a Web-based interface. The list changes often as softwarecomes and goes, so a listing here would probably very quickly go out of date. However, theMySQL Web site keeps a pretty comprehensive list at www.mysql.com/portal/software/ index.html. If you insist on using a Web GUI tool to design your databases, at least minimize the securityhazards by only using it on a development machine inside the firewall. After you ve fiddledwith the database design to your heart s content, you can initiate a database dump and thenmove the entire structure and code over to a production database server. Do not usePHPMyAdmin or any other Web GUI in production unless it is absolutely necessary. MySQL data typesThe actual PHP functions used to create MySQL databases are trivial compared to the MySQLdata structure statements that are passed in those functions. The Database Design sectionof Chapter13 has general rules on how to conceptualize a database schema and use the CREATE, DROP, and ALTERstatements. To implement your abstract schema in MySQL, how- ever, you also need to understand MySQL data types and how to use them. The general rule is to use the smallest and most specific data type that will adequately meetthe needs of this particular column in your database. MySQL is known for having compacttypes, such as TINYINTand TINYTEXT, that are good for things like 0/1 values or firstnames. It also has very large types that can store up to 4GB of data in one field. Caution18 #BREAK# 290Part IIPHP and MySQLThere are three buckets of MySQL data types: numeric types, date and time types, and string(or character) types. For the most part, their use can be fairly straightforward in the sensethat the average user is not going to know or care whether you used an INTor a MEDIUMINT. However, if you re the type of programmer who cares about doing everything in the abso- lutely tightest and fastest way possible, the MySQL manual gives subtle tips on maximizingefficiency for instance, always use the DECIMALtype with money, or it takes 8 bytes tostore a DATETIMEbut only 4 bytes to store a Unix TIMESTAMP, which PHP can convert to anydate-time format you desire. Careful perusal of the Column Types section of the MySQLmanual (at www.mysql.com/doc/en/Column_types.html) may yield hidden treasures ofinsight. Table 15-1 shows the current MySQL data types and their possible values. Mstands for themaximum number of digits displayed, and Dstands for the maximum number of decimalplaces in a floating-point number. Both are optional. Table 15-1: MySQL Data TypesName and AliasesStorage sizeUsageTINYINT(M) BIT, BOOL, BOOLEANare 1 byteIf unsigned, stores values from 0 to 255; synonyms for TINYINT(1)otherwise, from -128 to 127. A newBoolean type will appear in future, butuntil now has been implemented as aTINYINT(1). SMALLINT(M)2 bytesIf unsigned, stores values from 0 to 65535; otherwise, from -32768 to 32767. MEDIUMINT(M)3 bytesIf unsigned, stores values from 0 to16777215; otherwise, from -8388608 to8388607. INT(M) INTEGER(M)4 bytesIf unsigned, stores values from 0 to4294967295; otherwise, from -2147483648to 2147483647. BIGINT(M)8 bytesIf unsigned, stores values from 0 to18446744073709551615; otherwise, from -9223372036854775808 to9223372036854775807. You may experiencestrangeness when performing arithmeticwith unsigned integers of this size. FLOAT(precision)4 or 8 bytesWhere precision is an integer up to 53. Ifprecision <= 24, converted to a FLOAT; ifprecision > 24 and <= 53, converted to aDOUBLE. Provided for ODBC compatibility; in general, use the normal MySQL FLOATand DOUBLEtypes. FLOAT(M, D)4 bytesSingle-precision floating-point number. #BREAK# 291Chapter 15PHP/MySQL FunctionsName and AliasesStorage sizeUsageDOUBLE(M, D) DOUBLE PRECISION, REAL8 bytesDouble-precision floating-point number. DECIMAL(M,D) DEC, NUMERIC, FIXEDM+1 or M+2 bytesAn unpacked floating-point number that isstored like a CHAR. Used for smalldecimals, such as money. DATE3 bytesDisplayed in the format YYYY-MM-DD. DATETIME8 bytesDisplayed in the format YYYY-MM-DD. HH:MM:SS. TIMESTAMP4 bytesSince MySQL 4.1, can no longer set displaysize. Displayed in the same format asDATETIME. TIME3 bytesDisplayed in the format HHH:MM:SSwhereHHHis a value from -838 to 838. Thisallows a TIMEvalue to represent anelapsed time between two events. YEAR1 byteDisplayed in the format YYYY, which is avalue from 1901 to 2155. To use an earlierdate, you should use a TINYINTtype. CHAR(M)M bytesFixed in length. If your string is not longenough, it will be padded with spaces atthe end. M must be <= 255. VARCHAR(M)Up to M bytesVariable in length. M must be <= 255. TINYBLOBor TINYTEXTUp to 255 bytesTINYBLOBis case-sensitive for sorting andcomparison; TINYTEXTis case-insensitive. BLOBor TEXTUp to 64KBBLOBis case-sensitive for sorting andcomparison; TEXTis case-insensitive. MEDIUMBLOBor MEDIUMTEXTUp to 16MBMEDIUMBLOBis case-sensitive for sortingand comparison; MEDIUMTEXTis case- insensitive. LONGBLOBor LONGTEXTUp to 4GBLONGBLOBis case-sensitive for sorting andcomparison; LONGTEXTis case-insensitive. ENUM(value1,...valueN)1 or 2 bytesUp to 65535 distinct values. SET(value1,...valueN)Up to 8 bytesUp to 64 distinct values. MySQL FunctionsTable 15-2 includes a recap of the MySQL functions. All arguments in brackets are optional. #BREAK# 292Part IIPHP and MySQLTable 15-2: PHP-MySQL FunctionsFunction NameUsagemysql_affected_rows([link_id])Use after a nonzero INSERT, UPDATE, orDELETEquery to check number of rowschanged. mysql_change_user(user,password[,Changes MySQL user on an open link. database][,link_id]) mysql_close([link_id])Closes the identified link (usuallyunnecessary). mysql_connect([host][:port][:socket]Opens a link on the specified host, port, [,username][,password])socket; as specified user with password. All arguments are optional. mysql_create_db(db_name[,link_id])Creates a new MySQL database on thehost associated with the nearest open link. mysql_data_seek(result_id,row_num)Moves internal row pointer to specifiedrow number. Use a fetching function toreturn data from that row. mysql_drop_db(db_name[,link_id])Drops specified MySQL database. mysql_errno([link_id])Returns ID of error. mysql_error([link_id])Returns text error message. mysql_fetch_array(result_id[,result_type])Fetches result set as associative array. Result type can be MYSQL_ASSOC, MYSQL_NUM, or MYSQL_BOTH(default). mysql_fetch_field(result_id[,field_offset])Returns information about a field as anobject. mysql_fetch_lengths(result_id)Returns length of each field in a result set. mysql_fetch_object(result_id[,result_type])Fetches result set as an object. Seemysql_fetch_arrayfor result types. mysql_fetch_row(result_id)Fetches result set as an enumerated array. mysql_field_name(result_id,field_index)Returns name of enumerated field. mysql_field_seek(result_id,field_offset)Moves result pointer to specified fieldoffset. Used with mysql_fetch_field. mysql_field_table(result_id,field_offset)Returns name of specified field s table. mysql_field_type(result_id,field_offset)Returns type of offset field (for example, TINYINT, BLOB, VARCHAR). mysql_field_flags(result_id,field_offset)Returns flags associated withenumerated field (for example, NOTNULL, AUTO_INCREMENT, BINARY). #BREAK# 293Chapter 15PHP/MySQL FunctionsFunction NameUsagemysql_field_len(result_id,field_offset)Returns length of enumerated field. mysql_free_result(result_id)Frees memory used by result set (usuallyunnecessary). mysql_insert_id([link_id])Returns AUTO_INCREMENTEDIDofINSERT; or FALSEif insert failed or lastquery was not an insert. mysql_list_fields(database,table[,link_id])Returns result ID for use inmysql_fieldfunctions, withoutperforming an actual query. mysql_list_dbs([link_id])Returns result pointer of databases onmysqld. Used with mysql_tablename. mysql_list_tables(database[,link_id])Returns result pointer of tables indatabase. Used with mysql_tablename. mysql_num_fields(result_id)Returns number of fields in a result set. mysql_num_rows(result_id)Returns number of rows in a result set. mysql_pconnect([host][:port][:socket]Opens persistent connection to database. [,username][,password])All arguments are optional. Be careful mysql_closeand script terminationwill not close the connection. mysql_query(query_string[,link_id])Sends query to database. Remember toput the semicolon outside the double- quoted query string. mysql_result(result_id,row_id,Returns single-field result. Field identifier field_identifier)can be field offset (0), field name(FirstName) or table-dot name(myfield.mytable). mysql_select_db(database[,link_id])Selects database for queries. mysql_tablename(result_id,table_id)Used with any of the mysql_listfunctions to return the value referencedby a result pointer. SummaryPHP s MySQL and MySQL Improved functions are easy to use, if sometimes named confus- ingly. Each instance of a PHP/MySQL interaction must have a connection, a database select, and a query or command that returns a result identifier. The result identifier is like an ATMreceipt that reports on the success or failure of an operation. #BREAK# 294Part IIPHP and MySQLIf data is returned after a SELECTstatement, one of the PHP/MySQL fetching functions mustalso be employed. Data pulled from a MySQL database exists in a kind of limbo until one ofthe fetching functions is applied to the result set. If you wish to loop through the result setagain, you can use mysql_data_seek()to reset the row pointer to zero. PHP also has a large number of functions that return data about the database itself or about a particular operation. Two of the most common are mysql_num_rows(), which returns thenumber of rows in a result set; and mysql_insert_id(), which returns the ID of the proxi- mate INSERToperation. PHP handles much of the MySQL connectivity for you without requiring specific link identi- fiers or result pointers. The exception comes when you need multiple database connectionson the same Web page. In this case, you use exactly the same functions and syntax but simplypass the correct link identifier with most commands. We do not personally recommend creating MySQL databases with PHP front ends, but thepractice has become common. If you need to do so, you should follow a few specific rules. ... #BREAK# Displaying Queriesin TablesMuch of the point of PHP is to help you translate between aback end database and its front end presentation on the Web. Data can be viewed, added, removed, and tweaked as a result of yourWeb user s keystrokes and mouse clicks. For most of this chapter, we restrict ourselves to ways to use PHP to lookat the contents of a database without altering it, using onlythe SELECTstatement from SQL and displaying the results in HTMLtables. We use a single database example to show different strategies, including some handy reusable functions. Finally, we look at code tocreate the sample data shown in the display examples, using theINSERTstatement. The two big productivity points from this chapter are: .Reuse functionsin simple cases. The problem of database tabledisplay shows up over and over in database-enabled sitedesign. If the display is not complicated, you should be able tothrow the same simple function at the problem rather thanreinventing the wheel with each PHP page you write. .Choose between techniquesin complex cases. You may findyourself wanting to pull out a complex combination of informa- tion from different tables (which, of course, is part of the pointof using a relational database to begin with). You may not beable to map this onto a simple reusable function, but therearen t that many novel solutions either get to know the alter- natives, and you can decide how to trade off efficiency, read- ability, and your own effort. This chapter uses the MySQL database and functions exclusively, but the display strategies should be directly transferable to almostany SQL-compliant database supported by PHP. HTML Tables and Database TablesFirst of all, some terminology unfortunately, both relationaldatabases and HTML scripting use the term table, but the term meansvery different things in the two cases. A database table persistentlystores information in columns, which have predefined names andNote1616CHAPTER ...In This ChapterHTML tables andMySQL tablesComplex mappingsCreating the sampletables ...#BREAK# 296Part IIPHP and MySQLtypes so that the information in them can be recovered later. An HTML tableis a constructthat tells the browser to lay out the table s arbitrary HTML contents in a rectangular array in the browser window. We ll try to always make it clear which kind of table we are talkingabout. One-to-one mappingHTML tables are really constructed out of rows (the construct), and columnshave no independent existence each row has some number of table datum items (theconstruct), which will produce a nice rectangular array only if there are the samenumber of TDs for every TR. (There is no corresponding construct that lets you displayby column first.) By contrast, fields(aka columns) in database tables are the more primaryentity defining a table means defining the fields, and then you can add as many rows as youlike. In this chapter, we will focus on printing out tables and queries in such a way that eachdatabase field prints in its own HTML column, simply because there are usually more databaserows than database fields, and people are more used to up-and-down scrolling than left-to- right scrolling. If you find yourself wanting to map database fields to HTML rows, it is a simpleinversion exercise. The simplest case of display is where the structure of a database table or query doescorre- spond to the structure of the HTML table we want to display the database entity has mcolumns and nrows, and we d like to display an m-by-nrectangular grid in the user s browserwindow, with all the cells filled in appropriately. Example: A single-table displayerSo let s write a simple translator that queries the database for the contents of a single tableand displays the results on screen. Here s the top-down outline of how the code will get thejob done: 1.Establish a database connection. 2.Construct a query to send to the database. 3.Send the query and hold on to the result identifier that is returned. 4.Using the result identifier, find out how many columns (fields) there are in each row. 5.Start an HTML table. 6.Loop through the database result rows, printing a pair to make a corre- sponding HTML table row. 7.In each row, retrieve the successive fields and display them wrapped in a pair. 8.Close off the HTML table. 9.Close the database connection. Finally, we d like to wrap all the preceding steps up into a handy function that we can usewhenever we want to. Also, for reasons of efficiency, we don t want to include the first andlast steps of creating and closing the database connection in the function we may want touse such a function more than once per page, and it wouldn t make sense to open and closethe connection each time. Instead, we ll assume we have a connection already and pass theconnection to the function along with the table name. #BREAK# 297Chapter 16Displaying Queries in TablesSuch a function is shown in Listing 16-1, embedded in a complete PHP page that uses thefunction to display the contents of a couple of tables. Listing 16-1:A table displayern ); while ($row = mysql_fetch_row($result_id)) { print( ); for ($column_num = 0; $column_num < $column_count; $column_num++) print( $row[$column_num]n ); print( n ); } print( n ); } ?> Cities and countries
Some things to notice about this script: .Although the script refers to specific database tables, the display_db_table()func- tion itself is general. You could put the function definition in an includefile and thenuse it anywhere on your site. .The first thing the script does is load in an includefile that contains variable assign- ments for the database name, database username, and database password. It then uses19 #BREAK# 298Part IIPHP and MySQLthose variables to connect to MySQL and then to choose the desired database. (Thefact that this file is located outside the publicly available Web hierarchy makes itslightlymore secure than just including that information in your code.) .In the function itself, we chose to use a whileloop for printing rows and a forloop toprint the individual items. We could as easily have used a bounded forloop for bothand recovered the number of rows with mysql_num_rows(). .The main whileloop reflects a very common idiom, which exploits the fact that thevalue of a PHP assignment statement is the value assigned. The variable $rowisassigned to the result of the function mysql_fetch_row(), which will be either anarray of values from that row or a false value if there are no more rows. If we re out of rows, $rowis false, which means the value of the whole expression is false, whichmeans that the whileloop terminates. .We put line breaks (n) at the end of selected lines, so that the HTML source wouldhave a readable structure when printed or viewed as source from the browser. Noticethat these breaks are not HTML line breaks (
) and do not affect the look of theresulting Web page. (In fact, if you want to make it annoying for someone else to scruti- nize the HTML you generate, don t put breaks in at all!) The sample tablesTo see the Listing 16-1 script in action, see Figure 16-1, which shows the displayed contents ofthe Country and City sample tables. These tables have the following structure: Country: ID int (auto-incremented primary key) continent varchar(50) countryname varchar(50) City: ID int (auto-incremented primary key) countryID intcityname varchar(50) Think of these tables as a rough draft of the database for an eventual online almanac. Theyemploy our usual convention of always having one field per table called ID, which is a primarykey and has successive integers assigned to it automatically for each new row. Although youcan t tell for sure from the preceding description, the tables have one relation embodied intheir structure the countryIDfield of the Citytable is matched up with the IDfield of theCountrytable, representing which country the city belongs to. (If you were designing a realalmanac database, you would want to take this one step further and break the Countrytableinto a relational pair of Countryand Continenttables.) To see how we created these tables and populated them with sample data, see the Creatingthe Sample Tables section at the end of this chapter. Cross- Reference19 #BREAK# 299Chapter 16Displaying Queries in TablesFigure 16-1:A simple database table displayImproving the displayerOur first version of this function has some limitations: It works with a single table only, doesno error-checking, and is very bare-bones in its presentation. We ll address these problemsone by one and then fix them in one fell revision. (If you want to look ahead, the new-and- improved version of the function is in Listing 16-2.) Displaying column headersOur first version of a database table displayer simply displays all the table cells, without anylabeling of what the different fields are. It s conventional in HTML to use the element forcolumn and/or row headers in most browsers and styles, this displays as a bold table cell. One improvement we can make is to optionally display column headers that are based on thenames of the table fields themselves. To actually retrieve those names, we can use the functionmysql_field_name(). Error-checkingOur original version of the code assumes that we have written it correctly and also that ourdatabase server is up and functioning normally if either of these is not the case, we will runinto puzzling errors. We can partially address this by appending a call to die()to the actualdatabase queries if they fail, an informative message will be printed. This is a reasonableapproach for such a small example, but as projects get larger it is better to use the exception- handling capability introduced in PHP5. For an introduction to exception handling in PHP5, see Chapter 31.Cross- Reference19 #BREAK# 300Part IIPHP and MySQLCosmetic issuesAnother source of dissatisfaction with our simple table-displayer is that it always has thesame look. It would be nice, at a minimum, to control whether table borders are displayed. The simple solution we will use in our new function is just to permit passing in a string ofarguments that will be spliced into the HTML table definition. This is a pretty crude form ofstyle control that style-sheet proponents would discourage, but it will permit us to directlyspecify some elements of the table s look without writing an entirely new function. Displaying arbitrary queriesFinally, it would be nice to be able to exploit our relational database and display the results ofcomplex queries rather than just single tables. Actually, our single-table displayer has an arbi- trary query embedded in it it just happens that it is hard-coded as select*fromtable, where tableis the supplied table name. So let us transform our simple table-displayer into aquery-displayer and then recreate the table displayer as a simple wrapper around the querydisplayer. These two functions, complete with the cosmetic improvements and better error- checking, are shown in Listing 16-2. Listing 16-2:A query displayern ); // optionally print a bold header at top of tableif ($header_bool) { print( ); #BREAK# 301Chapter 16Displaying Queries in Tablesfor ($column_num = 0; $column_num < $column_count; $column_num++) { $field_name = mysql_field_name($result_id, $column_num); print( $field_name ); } print( n ); } // print the body of the tablewhile ($row = mysql_fetch_row($result_id)) { print( ); for ($column_num = 0; $column_num < $column_count; $column_num++) { print( $row[$column_num]n ); } print( n ); } print( n ); } function display_db_table($tablename, $connection, $header_bool, $table_params) { $query_string = SELECT * FROM $tablename ; display_db_query($query_string, $connection, $header_bool, $table_params); } ?> Countries and cities
The result of using this code on the same database contents is shown in Figure 16-2. The onlyvisible difference is the column header. Splitting the functions apart means that we also havea new function in our bag of tricks we could do the same kind of display with an arbitraryquery string that joins data from different tables. #BREAK# 302Part IIPHP and MySQLFigure 16-2:Using the query displayerComplex MappingsSo far in this chapter, we ve enjoyed a very nice and simple-minded correspondence betweenquery result sets and HTML tables every row in the result set corresponds to a row in thetable, and the structure of the code is simply two nested loops. Unfortunately, life isn t oftenthis simple, and sometimes the structure of the HTML table we want to display has a complexrelationship to the relational structure of the database tables. Multiple queries versus complex printingLet s say that, rather than displaying our sample Cityand Countrytables individually, wewant to match them up in a tabular display. We can easily write a SELECTstatement that joins these tables appropriately: SELECT country.continent, country.countryname, city.citynameFROM country, cityWHERE city.countryID = country.IDORDER BY continent, countryname, citynameNow, this would be a handy place to use our query-displayer function all we have to do issend it the preceding statement as a string, and it will print out a table of cities matched upwith their continents and countries. However, if we do this, we will see an individual HTMLtable row for each city, and the continent and country will print each time for example, we ll see North Americaprinted several times. Instead, what if we want one name matchedwith many titles? This is a case where the structure of what we print differs from the struc- ture of the most convenient query. #BREAK# 303Chapter 16Displaying Queries in TablesIf we want to do a more complex mapping, we have a choice: We can throw database queriesat the problem, or we can write more complex display code. Let s look at each option in turn. (For each of these examples, we ll be moving away from the reusable generality of the func- tions we wrote earlier toward functions that address a particular display problem.) A multiple-query exampleIf we want to print just one HTML row per country, we can make a query for the countriesand then make another query for the relevant cities in each trip through a countryrow. Afunction written using this strategy is shown in Listing 16-3. Listing 16-3:A display with multiple queriesn ); ContinuedViews and Stored ProceduresOur query-displayer assumes a particular division of labor between the PHP code and thedatabase system itself the PHP code sends off an arbitrary query string, which the databaseresponds to by setting up a result set. In particular, this means that the database system has toparse that query and then figure out the best way to go about retrieving the results. This is partof what can make querying a database a mildly expensive operation. In cases where your code may construct novel queries on the fly, this is the best you can hopefor. However, some databases offer ways to set up queries in advance, which gives the databasesystem a chance to preoptimize how it handles the query. One such construct is called a viewunder MS SQL Server and some other RDMSs after you have set up a query as a named view, itcan be treated just like a real table. A related idea is the stored procedure, which is like a viewthat also accepts runtime arguments that are spliced into the query. In general, if you realize thatyou are suffering from slow query performance, you may want to investigate what optimizationslike this your particular RDBMS makes available. #BREAK# 304Part IIPHP and MySQLListing 16-3(continued) print( ContinentCountry Cities ); /* loop through countries */ while ($country_row = mysql_fetch_row($country_result)) { /* set up country info */ $country_id = $country_row[0]; $continent = $country_row[1]; $country_name = $country_row[2]; print( ); print( $continent ); print( $country_name ); /* begin table cell for city list */ print( ); $city_query = select cityname from citywhere countryID = $country_idorder by cityname ; $city_result = mysql_query($city_query, $db_connection) OR die(mysql_error()); /* loop through cities */ while ($city_row = mysql_fetch_row($city_result)) { $city_name = $city_row[0]; print( $city_name
); } /* close city cell and country row */ print( ); } print( n ); } ?> Cities by Country #BREAK# 305Chapter 16Displaying Queries in TablesThe strategy is appealingly simple: There is an outer loop that uses one query to proceedthrough all the countries, saving the country s name and the primary IDfield of each countryrow. Then for each country, the IDfield is used to look up all the cities belonging to thatcountry. Notice the trick of embedding the $countryidvariable in the inner query thequery string sent is actually different on each iteration through the countryloop. Simple? Yes. Efficient? Probably not. This code makes a separatecity query for each country. If there are 500 countries in the database, this function will make 501 separate databasequeries (the extra one being the enclosing country query). Your mileage will vary according to how efficient your particular database is in parsingqueries and planning query retrieval, but the sum of these queries will certainly take moretime than the simple query we started this section with. A complex printing exampleNow let s solve exactly the same problem, but using a different strategy. Instead of makingmultiple queries, we will make a single query and print the resulting rows selectively, so thateach HTML table row corresponds to more than one database row (see Listing 16-4). Theresulting browser display is exactly the same as in the previous example. Listing 16-4:A complex display with a single queryn ); Continued19 #BREAK# 306Part IIPHP and MySQLListing 16-4(continued) print( ContinentCountry Cities ); /* Initialize the ID for the previous country. We will rely on the fact that Country.ID isnumbered beginning with 1, so a previous IDvalue of zero means that the current countryis the first */ $old_country_id = 0; /* loop through result rows (one per city) */ while ($row_array = mysql_fetch_row($result_id)) { $country_id = $row_array[0]; /* if we have a new country */ if ($country_id != $old_country_id) { /* set up country info */ $continent = $row_array[1]; $country_name = $row_array[2]; /* if there was a previous countryclose the city datum and country row */ if ($old_country_id != 0) print( n ); /* start a row for the new country, and begin the city table datum */ print( ); print( $continent ); print( $country_name ); /* the new country is no longer new */ $old_country_id = $country_id; } /* the only thing that is printed for every resultrow is the name of a city */ $city_name = $row_array[3]; print( $city_name
); } /* close off final country and table */ print( ); } ?> Cities by Country #BREAK# 307Chapter 16Displaying Queries in TablesThis code is somewhat tricky although it goes through the result rows in order, and every- thing it prints is grabbed from the current row, it prints countries only when their values havechanged. (Continents are still printed redundantly.) The change in a country is detected by monitoring the IDfield of the countryrow. A countrychange is also a signal to print out the HTML necessary to close off the preceding table rowand start a new one. Finally, the code must handle printing the HTML necessary to start thefirst row and end the last one. Creating the Sample TablesNow we will show you the PHP/MySQL code we actually used to create the sample tables. (Such data might more normally be created by interacting only with MySQL, but we decidedto respect our book s title by doing it from PHP.) The code (shown in Listing 16-5) is a special- purpose, one-time hack, not a model of style, but it has useful examples of using the SQLINSERTstatement. Listing 16-5:Creating the sample tables ); } ?> Creating a sample database You should be able to use this code to recreate the sample database on your developmentmachine, assuming that you have PHP and MySQL configured, and an appropriately locatedfile called phpbook-vars.inccontaining username, password, and database-name strings. Just as in the display examples, this code sends off query strings (with embedded variables), but this time the queries are INSERTstatements, which create new table rows. For the mostpart, the data inserted is just string data passed in to the function, although we chose to passin an arbitrary number of cities per country by using an array. #BREAK# 309Chapter 16Displaying Queries in TablesThe only tricky thing in creating these sample tables is setting up the relational structure. We want each cityrow to have an appropriate countryID, which should be equal to theactual IDof the appropriate row from the country table. However, these countryIDs areautomatically assigned in sequence by MySQL and are not under our control. How can weknow the right countryIDto assign? The answer is in the incredibly handy functionmysql_insert_id(), which recovers the IDassociated with the last INSERTquery made via the given database connection. We insert the new country, recover the IDof the newlycreated row, and then use that IDin our city insertion queries. SummaryDatabase interaction is one of the areas where PHP really shines. One very common use fordatabase-enabled Web code is simply to display database contents attractively. One approachto this kind of display is to map the contents of database tables, or SELECTstatements, tocorresponding HTML table elements. When the mapping is simple enough, you can employ reusable functions that take arbitrarytable names, or SELECTstatements, and display them as a grid. When you need a more complicated combination of information from relational tables, you probably need a special- purpose function, but certain tricks recur there as well. One such trick is to craft a SQL state- ment that returns all the information you need, in the order you want, and selectively printonly the nonredundant portions. Near the end of this chapter, we saw a quick example of populating a set of database tablesusing INSERTstatements. Aside from that, all the techniques in this chapter were read-onlyand do not modify the contents of databases at all. In Chapter 17, we ll see how you can get a more intimate connection to your database by combining SQL queries with HTML forms. … #BREAK# Building Formsfrom QueriesForm handling is one of PHP s very best features. The combinationof HTML to construct a data-input form, PHP to handle the data, and a database server to store the data lies at the heart of all kinds ofsupremely useful Web tasks. HTML FormsYou already know most of what you need to make good forms to behandled by PHP and a database. There are a few PHP-specific pointsto brush up on: .Always, always, alwaysuse a NAMEfor every data entry element(INPUT, SELECT, TEXTAREA, and so on). These NAMEattributeswill become PHP variable names you will not be able toaccess your values if you do not use a NAMEattribute for eachone. If your WYSIWYGeditor doesn t allow you to do this, you llneed to remember to add these NAMEattributes by hand. .A form field NAMEdoes not need to be the same as the corre- sponding database field name, but it s often a good idea. .You can (and usually should) specify a VALUErather than letHTML send the default value. Consider substituting a numeri- cal value for a text value if possible, because the database ismuch slower at matching strings than integers. .The VALUEcan be set to data you wish to display in the form. .Remember that you can pass hidden variables from form toform (or page) using the HIDDENdata entry elements. Thispractice may have negative security implications, but it s oftenno worse than storing the data in a cookie. .Remember that you can pass multiple variables in an array but you need to inform the user that this is a possibility. See Chapter 7 for more information on how to format an HTMLform for use with PHP. Cross- Reference1717CHAPTER …In This ChapterUnderstanding HTML formsSubmitting data via formsSelf-submitting formsEditing data with an HTML form …#BREAK# 312Part IIPHP and MySQLBasic Form Submission to a DatabaseSubmitting data to a database via an HTML form is straightforward if the form and form- handler are two separate pages. Listing 17-1, newsletter_signup.html, is a simple formwith only one input field. Listing 17-1:A simple form (newsletter_signup.html)

Newsletter sign-up form

Enter your email address and we will send you ourweekly newsletter.



Figure 17-1 shows the result of the preceding code sample, a basic form to insert data into adatabase. You enter the data in the database and acknowledge receipt in the form handler in Listing 17-2, which (with great originality) we are calling formhandler.php. #BREAK# 313Chapter 17Building Forms from QueriesFigure 17-1:A form to insert data into a databaseListing 17-2:Form handler for newsletter_signup.html(formhandler.php)

Newsletter sign-up form

30) { echo

There is a problem. Did you enter an emailaddress?

; } else { Continued20 #BREAK# 314Part IIPHP and MySQLListing 17-2(continued) // Open connection to the databasemysql_connect( localhost , phpuser , sesame ) or die( Failure to communicate with database ); mysql_select_db( test ); // Insert email address$as_email = addslashes($_POST[ email ]); $tr_email = trim($as_email); $query = INSERT INTO mailinglist (ID, Email, Source) VALUES(NULL, $tr_email , www.example.com/newsletter_signup.html ) ; $result = mysql_query($query); if (mysql_affected_rows() == 1) { echo

Your information has been recorded.

; } else { error_log(mysql_error()); echo

Something went wrong with your signupattempt.

; } } ?>
Having a separate form and form handler is a very clean design that can potentially be easierto maintain. However, there are quite a few things you might want to do that you can t do easilywith this model, caused by the difficulty of going back to the form from the form handler andthe fact that variables are not available to both at the same time. For one thing, if something goes wrong with the submission, it s very difficult to redisplay theform with the values you just filled in. This is particularly important with something like a userregistration form, where you might want to check for unique e-mail addresses or matchingpasswords and reject the entire registration with an error message if it doesn t pass the tests. People are going to be very annoyed if one little typo causes them to lose all the data thatthey just filled in and after one or two go-rounds, they will simply stop trying to register. The first step to solving all these problems is to combine form and handler into one self- submitting PHP script. Self-SubmissionSelf-submission is not a new form of autoeroticism. It simply refers to the process of combin- ing one or more forms and form-handlers in a single script, using the HTMLFORMstandard tosubmit data to the script one or more times. #BREAK# 315Chapter 17Building Forms from QueriesAnother situation in which self-submission is a win is when you need to submit the same formmore than once. Say you are applying for auto insurance online, and you need to give the par- ticulars of three or four different cars. It s extra work for the user to submit the form, get asuccess message, and then have to click a button to go back to the form for car #2. This kindof navigation problem has no perfect solution, but in situations where there s a high probabil- ity of multiple submissions, self-submission causes fewer clickthroughs for your Web users. Finally, the separate form and form handler make it difficult to pull data from the database, edit it, and submit it repeating the process however many times it takes for the user to besatisfied. A common example of this usage is a form to allow users to change their personalinformation, such as photos and bios, which people often like to fiddle with until they lookexactly like the users want. If you want to make five small incremental edits to your user pro- file, you aren t going to want to go back and forth between form and form-handler ten times. Self-submission is accomplished by the simplest of means: specifying the same script nameas the ACTIONtarget in the FORM element like this:
Or, using a built-in feature unique to PHP: > Although you always have the option to just use the file s URL, the built-in $_SERVER[ PHP_ SELF ]variable is preferable. Then the file will continue to be handled correctly if yourename or move it (into a PHP-enabled directory, needless to say). $_SERVER[ PHP_SELF ] is the replacement for $PHP_SELF, which was available from all PHP scripts until version4.2.0. The single most important thing to remember about self-submitting forms is: The logic comesbefore the display. If you re used to writing separate forms and handlers, this may seem a littlecounterintuitive at first but think of it this way: Because your form will look different or display variables based on interactions with the database, obviously these interactions musthappen before the HTML for the page is output to the browser. After you construct a few self- submitting forms, logic-before-display will seem totally natural and painless. To use self-submission with controls, you will need to employ a more programmatic PHP- writing style what we term the maximumor mediumstyle. Beginners may find this some- what more difficult than a clear division between the functions of HTML (form display) andPHP (form handling). This can be mitigated somewhat by using the heredocsyntax, as we doin many of our examples. If you re a think-ahead type, by now you re wondering: BUT if the logic comes before the dis- play, won t my script try to do the database operations before showing me the HTML form inthe first place? Good question and an indication that we need some way to tell the scripteither We want to see the form now or We want to insert data into the database now. This What am I supposed to be doing now? bit is called a stage variable. It lets you keep track ofhow many times the form has submitted values to itself and, therefore, which stage of a multi- step process you have reached. The cheapest stage variable to test for is the Submit button. You can name your Submit buttonand give it a value, which will be set as a PHP value only after the form is submitted at leastonce. The easiest way to demonstrate what we re talking about is by rewriting the previousform and form-handler as one self-submitting form, as we do in Listing 17-3. CautionTip20 #BREAK# 316Part IIPHP and MySQLListing 17-3:Unified form and form-handler (newsletter_signup.php) 30)) { $message =

There is a problem. Did you enter an emailaddress?

; } else { // Open connection to the databasemysql_connect( localhost , phpuser , sesame ) or die( Failure to communicate with database ); mysql_select_db( test ); // Insert email address$as_email = addslashes($_POST[ email ]); $tr_email = trim($as_email); $query = INSERT INTO mailinglist (ID, Email, Source) VALUES(NULL, $tr_email , www.example.com/newsletter_signup.html ) ; $result = mysql_query($query); if (mysql_affected_rows() == 1) { $message =

Your information has been recorded.

; $noform_var = 1; } else { error_log(mysql_error()); $message =

Something went wrong with your signupattempt.

; } } // Show the form in every case except successful submissionif (!$noform_var) { $thisfile = $_SERVER[ PHP_SELF ]; $message .= <<< EOMSG

Enter your email address and we will send you our weeklynewsletter.



EOMSG; } } ?> #BREAK# 317Chapter 17Building Forms from Queries

Newsletter sign-up form

The first time you load up this page, you should see a normal HTML form exactly like the onein Figure 17-1. If you submit it without any data or with a string that s too long (often a sign ofa cracking attempt), you ll see an error message and the form again. If something goes wrongwith the database INSERT, you ll see an error message and the form again. Only if the INSERTcompletes successfully will you not see the form again which is the navigation we wantbecause we don t want people to sign up for the newsletter more than once. In the preceding example, we need to check only for two states of the form (unsubmitted orsubmitted) so we can use the Submit button as our stage variable. But what if you want tocheck for more than one state? You need a variable that is capable of taking more than onevalue. You could either give your Submit button different values, which would show up as dif- ferent labels in the button itself, or you could set a hidden variable that is capable of takingmore than one value depending on the state. We demonstrate the technique in Listing 17-4, which collects some information and then allows you to rate your boss anonymously. Listing 17-4:A three-part form (rate_boss.php) We must ask for your name and email address to ensure that noone votes more than once, but we do not associate your personalContinued20 #BREAK# 318Part IIPHP and MySQLListing 17-4(continued) information with your rating.

Name:

Email:

EOREGFORM; $rate_form = <<< EORATEFORM

My boss is:

Driving me to look for a new job.
Not the worst, but pretty bad.
Just so-so.
Pretty good.
A pleasure to work with.

Boss s name:


EORATEFORM; if (!$_POST[ submit ]) { // First time, just show the registration form$message = $reg_form; } elseif ($_POST[ submit ] == Submit && $_POST[ stage ] == register ) { // Second time, show the registration form again on error, // rating form on successful INSERTif (!$_POST[ name ] || $_POST[ name ] == || strlen($_POST[ name ] > 30) || !$_POST[ email ] || $_POST[ email ] == || strlen($_POST[ email ] > 30)) { $message =

There is a problem. Did you enter a name andemail address?

; $message .= $reg_form; } else { // Open connection to the databasemysql_connect( localhost , phpuser , sesame ) or die( Failure to communicate with database ); #BREAK# 319Chapter 17Building Forms from Queriesmysql_select_db( test ); // Check to see this name and email have not appeared before$as_name = addslashes($_POST[ name ]); $tr_name = trim($as_name); $as_email = addslashes($_POST[ email ]); $tr_email = trim($as_email); $query = SELECT sub_id FROM ratersWHERE Name = $tr_name AND Email = $tr_email ; $result = mysql_query($query); if (mysql_num_rows($result) > 0) { error_log(mysql_error()); $message = Someone with this name and password hasalready rated . If you think a mistake was made, please emailhelp@example.com. ; } else { // Insert name and email address$query = INSERT INTO raters (ID, Name, Email) VALUES(NULL, $tr_name , $tr_email ) ; $result = mysql_query($query); if (mysql_affected_rows() == 1) { $message = $rate_form; } else { error_log(mysql_error()); $message =

Something went wrong with your signupattempt.

; $message .= $reg_form; } } } } elseif ($_POST[ submit ] == Submit && $_POST[ stage ] == rate ) { // Third time, store the rating and boss s name// Open connection to the databasemysql_connect( localhost , phpuser , sesame ) or die( Failure to communicate with database ); mysql_select_db( test ); // Insert rating and boss s name$as_boss = addslashes($_POST[ boss ]); $tr_boss = trim($as_boss); $rating = $_POST[ rating ]; $query = INSERT INTO ratings (ID, Rating, Boss) VALUES(NULL, $rating , $tr_boss ) ; $result = mysql_query($query); Continued20 #BREAK# 320Part IIPHP and MySQLListing 17-4(continued) if (mysql_affected_rows() == 1) { $message =

Your rating has been submitted.

; } else { error_log(mysql_error()); $message =

Something went wrong with your ratingattempt. Try again.

; $message .= $rate_form; } } ?>

Rate your boss anonymously

Figure 17-2 shows the rating form after an error has occurred. Some of you might be thinking, Hey, wait! You said logic always comes before display butthen you started this script with a bunch of HTML. Very perspicacious but not quite right. Look closely and you will realize that we are merely setting a bunch of text to a couple of variable strings ($reg_formand $rate_form). In the entire PHP section, we actually don tdisplay anything. We merely construct a string, $message, which will be plugged in to theHTML at the bottom. If we took away the HTML, you would see a blank page in the browser. So it s OK to assemble the text you re going to want to display in the logic part; just don techo it out to the browser until the end. #BREAK# 321Chapter 17Building Forms from QueriesFigure 17-2:A multiple self-submitting formAnother issue with self-submitted forms is navigation. With the traditional HTML form, naviga- tion is strictly one-way: form to handler to whatever navigational device (if any) the designerdecrees. Self-submitted forms need not conform to this rule, however. In each individualinstance, you need to decide: .Whether the form can be resubmitted multiple times by the user, in whole or in part. .Whether the user decides when to move on by clicking a link or the form moves usersalong automatically. .Whether you need to pass variables on to the next page, hidden or in plain view. .Whether you want to control where the user can go next or if you want to give usersmultiple choices. All these techniques are most commonly used in user-management functions registration, login, and editing user information which are demonstrated realistically in Chapter 44. The answers to these questions will determine whether you need a control, another form, asimple link or button, or multiple links. Whatever you decide about navigation, remember to provide plenty of text that clearly explainswhat s going to happen at every step. Because PHP gives you so much flexibility with forms, new users default expectations may be crossed up, and they could end up uncertain whetherthey accomplished their mission with your form. TipCross- Reference20 #BREAK# 322Part IIPHP and MySQLEditing Data with an HTML FormPHP is brilliant at putting variables into a database, but it really shines when taking data froma database, displaying it in a form to be edited, and then putting it back in the database. ItsHTML-embeddedness, easy variable-passing, and slick database connectivity are at their bestin this kind of job. These techniques are extremely useful, because you will find a millionoccasions to edit data you re storing in a database. Let s look at the specific kinds of HTML FORMdata elements and how they are handled. TEXT and TEXTAREATEXTand TEXTAREAare the most straightforward types because they enjoy an unambiguousone-to-one relationship between identifier and content. In other words, there is only one pos- sible VALUEper NAME. You just pull the data field from the database and display it in the formby referencing the appropriate array value, as shown in Figure 17-3. Figure 17-3:Displaying text for editingListing 17-5, comment_edit.php, takes a comment out of the database and allows you to edit it. You may need to use the stripslashesfunction when displaying TEXTAREAand TEXTifthere s any chance the values might have single quotes or apostrophes and magic_quotes_ gpcis on. Watch out for people with apostrophe d names like O Malley or D Nesh! Tip20 #BREAK# 323Chapter 17Building Forms from QueriesListing 17-5:Editing data from database (comment_edit.php) Your comment has been updated.

; } else { error_log(mysql_error()); $success_msg =

Something went wrong.

; } } else { // Get the comment header and comment$comment_id = $_GET[ comment_id ]; $query = SELECT comment_header, commentFROM commentsWHERE ID = $comment_id ; $result = mysql_query($query); $comment_arr = mysql_fetch_array($result); $comment_header = stripslashes($comment_arr[0]); $comment = stripslashes($comment_arr[1]); } $thispage = $_SERVER[ PHP_SELF ]; //Have to do this for heredoc$form_page = <<< EOFORMPAGE

Comment edit

$success_msg




EOFORMPAGE; echo $form_page; ?> Remember that in an HTML form integers and doubles must use the TEXTor TEXTAREAtype, as there is no specifically numeric HTML form field type. CHECKBOXThe CHECKBOXtype has only one possible value per input: off (unchecked) or on (checked). The database field which records this information is almost always going to be a small inte- ger or bit type with values 0and 1corresponding to unchecked or checked check boxes. Figure 17-4 shows a common type of check box being edited. Listing 17-6 demonstrates how to use a check box to display and change a Boolean value. Tip20 #BREAK# 325Chapter 17Building Forms from QueriesFigure 17-4:A prepopulated check boxListing 17-6:Checkbox displaying boolean data from database(optout.php) Your preference has been updated.

; } else { error_log(mysql_error()); $success_msg =

Something went wrong.

; } // Get the value$query = SELECT BoxValue FROM checkboxWHERE BoxName = OptOut AND email = $as_email ; $result = mysql_query($query); $optout = mysql_result($result, 0, 0); if ($optout == 0) { $checked = ; } elseif ($optout == 1) { $checked = CHECKED ; } } // Now display the page$thispage = $_SERVER[ PHP_SELF ]; //Have to do this for heredoc$form_page = <<< EOFORMPAGE Semi-sleazy opt-in form $success_msg
Email address:

Please send me lots of e-mail bulletins!
opt out by clicking this tiny checkbox

EOFORMPAGE; echo $form_page; ?> #BREAK# 327Chapter 17Building Forms from QueriesAlthough each check box is capable of expressing only a fixed chunk of data, check boxes areoften used in bunches to convey more complex aggregate meanings. Look at the check boxgrouping in Figure 17-5. Figure 17-5:A cluster of check boxesForms with large numbers of check boxes like this are more work for the Web developmentteam, but they provide a nice interface for the user. The code for this page is very similar tothat of the previous example, with more variables. You can download it from the Web site forour book at www.troutworks.com/phpbook/. RADIORADIOdata elements allow for a one-to-many relationship between identifier and value. Inother words, they have multiple possible values, but only one can be predisplayed or selected. They are best for small sets of options, generally between two and ten, which need more thana word or two of text to identify themselves. Unfortunately, it s somewhat more difficult to represent stored data in a radio button than ina check box or text field. This is because there is only one possible value for a text or textareaand only two possible values for a check box but radio buttons can have more than twopossible values. Therefore, you will have to output part of the actual form with PHP. This looksa little bit less neat than the styles we employed previously, so you have to go to a little moretrouble to have an easily readable script. Again, the user-interface experience allowed by radiobuttons is worth the extra trouble it gives to the Web developer. In the example in Figure 17-6 and accompanying code, we are assembling a series of radiobuttons that display preference data from the database. #BREAK# 328Part IIPHP and MySQLFigure 17-6:Prepopulated radio buttonsListing 17-7 shows the code for Figure 17-6, which shows how to edit forms with radio buttons. Listing 17-7:Radio buttons displaying boolean data from database(date_prefs.php) Your preferences have been updated.

; } else { error_log(mysql_error()); $success_msg =

Something went wrong.

; } } // Get the values$query = SELECT height, haircolor, edu FROM qualitiesWHERE subscriber = $sub_id ; $result = mysql_query($query); $pref_arr = mysql_fetch_array($result); $height = $pref_arr[0]; $haircolor = $pref_arr[1]; $edu = $pref_arr[2]; // Assemble the radio button part of the formif ($height == 1) { $radio_str .= Short
n ; } else { $radio_str .= Short
n ; } if ($height == 2) { $radio_str .= Average height
n ; } else { $radio_str .= Average height
n ; } if ($height == 3) { $radio_str .= Tall
n ; } else { $radio_str .= Tall
n ; } if ($height == 0) { $radio_str .= Doesn t matter

n ; } else { $radio_str .= Doesn t matter

n ; } if ($haircolor == 1) { Continued20 #BREAK# 330Part IIPHP and MySQLListing 17-7(continued) $radio_str .= Blonde
n ; } else { $radio_str .= Blonde
n ; } if ($haircolor == 2) { $radio_str .= Brunette
n ; } else { $radio_str .= Brunette
n ; } if ($haircolor == 3) { $radio_str .= Redhead
n ; } else { $radio_str .= Redhead
n ; } if ($haircolor == 0) { $radio_str .= Doesn t matter

n ; } else { $radio_str .= Doesn t matter

n ; } if ($edu == 1) { $radio_str .= High school graduate
n ; } else { $radio_str .= Highschool graduate
n ; } if ($edu == 2) { $radio_str .= College graduate
n ; } else { $radio_str .= Collegegraduate
n ; } if ($edu == 3) { $radio_str .= Advanced degree holder
n ; } else { $radio_str .= #BREAK# 331Chapter 17Building Forms from QueriesAdvanced degree holder
n ; } if ($edu == 0) { $radio_str .= Doesn t matter

n ; } else { $radio_str .= Doesn tmatter

n ; } // Now display the page$thispage = $_SERVER[ PHP_SELF ]; //Have to do this for heredoc$form_page = <<< EOFORMPAGE

Dating service

$success_msg

I am looking for a girl who is:

$radio_str
EOFORMPAGE; echo $form_page; ?> #BREAK# 332Part IIPHP and MySQLSELECTThe SELECTfield type is perhaps the most interesting of all. It can handle the largest numberof options, and it also allows the user to select multiple options that can be passed back tothe database using arrays. See Chapter 38 for ideas about using JavaScript to make even more interesting SELECTforms. In Figure 17-7, we are using the SELECTform element with multiple options. In PHP, this isdone by creating an array of the multiple selected option values to pass to the form handler. You set up the array in the HTML form by declaring the MULTIPLEattribute of the SELECTele- ment and by naming the SELECTelement something like $val[] in other words, appendinga set of square brackets to the variable name. This will indicate to PHP that it s dealing withan array rather than a single variable, and it will construct the array appropriately with themultiple selected values. When the array gets to the form handler, you will need to deal withthe values as you would any array s values by dereferencing, or by listing out the contentsof the array. Figure 17-7:A prepopulated select with multiple choicesListing 17-8 shows the code for Figure 17-7, which demonstrates how to display and edit aselect list with multiple options. Cross- Reference20 #BREAK# 333Chapter 17Building Forms from QueriesListing 17-8:Select list displaying database values (skills_profile.php) Something went wrong

; break; } } } // Get all the results$query = SELECT * FROM skills ; $result = mysql_query($query); // Download this user s skills$query1 = SELECT skill_idFROM user_skillWHERE user_id = $user_id ; $result1 = mysql_query($query1); while ($user_skill = mysql_fetch_array($result1)) { $skill_id = $user_skill[0]; $user_skill_arr[$skill_id] = $skill_id; } while ($skills = mysql_fetch_array($result)) { Continued20 #BREAK# 334Part IIPHP and MySQLListing 17-8(continued) $key = $skills[0]; if ($key == $user_skill_arr[$key]) { $select_str .=