ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


PHP Forms

PHP With HTML Forms

12/01/2000

In the last exciting episode of the PHP Phanatics, we explored the inner workings of PHP's unique $GLOBALS array. Several sharp-eyed readers pointed out a bug in the script dumping the contents of the $GLOBALS array. If an array element was itself an array, the display listed that fact rather than the contents of the component array. The displayed line looked like:

$GLOBALS[HTTP_GET_VARS]=Array

In my defense I plead confusion. There were elements on both sides of the array. In addition, the vertical alignment was less than perfect. It was very difficult determining which array component a pointer was addressing.

A generalized array-dumping function is discussed at the end of this article. The developed function displays any one- or two-dimensional array.

In this episode we'll continue our exploration of PHP forms, becoming more aggressive and bolder as we go.

Expanding our bag of form tricks

Last time our introductory form contained two text inputs and a multiple-choice drop menu. The first form had only rudimentary error checking, displaying only a message indicating that a required field was not entered. In the form about to be developed, we'll add a few radio buttons, a check box, a text area, and a drop menu limited to allowable states.

A few additional goodies will include a fairly robust error-handling function that displays specific errors. The best parts of the new form are functions to create radio button and text box groupings containing any number of buttons or boxes. Both functions also will display the current selected status of a radio button or text box grouping if available.

Drop menu builder

Script form-two.php contains a function named MakeStateDropMenu(). As the name suggests, the function builds a state drop menu. For good measure, possessions with postal service abbreviations and APO/FPO options are included. The function has one required and one optional parameter. The first parameter is the form name associated with the drop menu, such as "CustomerState." The optional parameter contains the state abbreviation to be initially selected. If the second parameter is not specified, no state is selected.

Menu building starts with:

$DropMenu=<<<dropmenu
<select name="$Name" size="1">
  <option value="AL">Alabama</option>
 ...
  <option value="AP">AP-APO/FPO</option>
</select>
DropMenu;

It terminates with:

if ($Selected) {
  $DropMenu = preg_replace("|\"$Selected\">*|",
     "\"$Selected\" SELECTED>",$DropMenu);
} # if ($Selected)
return $DropMenu;

The function preg_replace() performs a Perl-type regular expression replacement. Let's assume "NJ" was passed as the second parameter when calling the function. The entire menu string is searched for "NJ"> and upon finding:

<option value="NJ">New Jersey</option>

"NJ"> is replaced with "NJ" SELECTED>. Cool, no?

You can prime the pump with something like:

$Selected = ($State) ? $State : "NJ";

If $State, the drop menu NAME field, has a value, use it. If not, use NJ. If no state is to be pre-selected, call the function with something like:

$StateMenu = MakeStateDropMenu("State",$Selected);

Drop menus, when the choices are pre-determined and fairly limited, insure that any user selection is valid.

A variation on the drop menu theme could be employed to populate the SELECT options from a database.

Radio buttons

The purpose of a radio button grouping is to select one item from a mutually exclusive group. Although the buttons can be initialized with no selection, a maximum of one item is selected. If one button is selected and another button is checked, the selection on the first button will disappear. The NAME parameters for a radio button grouping are all identical, and the content of the variable is determined by the VALUE clause of the selected button.

A generalized radio button builder would be nice, so let's build one. Our first set of radio buttons on the new form requests the submitter's gender. The function SetRadioButtons() has four parameters; the first two are required and the second two are optional. The first positional parameter contain the value used for the NAME parameter. All radio buttons in a grouping share a common NAME clause. The content of the VALUE clause associated with the user's selection is transmitted to the script.

The second positional parameter is the prompt value to be initially checked. The second parameter should contain a null field ("") if there is to be no initial selection. The third parameter (optional) is an array of possible values. The default is (1,0). The last parameter (optional) is an array of prompts. The default is ("Yes","No"). The relative position in the prompt array should correspond with the values array. There can be any number of buttons in the radio button grouping. Calling the function with:

SetRadioButtons("Gender","Male",array("M","F"),array("Male:","Female:"));

produces the following HTML code:

Male: <input type="RADIO" name="Gender" value="M" checked>
Female: <input type="RADIO" name="Gender" value="F">

If the form is to be redisplayed with the submitted value displayed, call the function with:

SetRadioButtons("Gender",$Gender,array("M","F"),array("Male:","Female:"));

If the function is called using the shortest parameters list:

SetRadioButtons("Married","1") the returned string would be:

Yes: <input type="RADIO" name="Married" value="1" checked>
No: <input type="RADIO" name="Married" value="0">

Check boxes

The purpose of a check box grouping is allowing the selection of zero or more items from a list whose items are not mutually exclusive. "Check all that apply" comes to mind as a good heading. As with radio buttons, check boxes can be initially selected.

The function MakeCheckBoxes() uses several tricks to manage the group. Although the NAME parameters in a group of check boxes can all be different, that would be very difficult to generalize. In the MakeCheckBoxes() function, the NAME clause of each contains an array element. The function has three parameters; the first two are required and the last is optional. The first parameter holds the value used for the NAME cause. The second parameter is an array of values used for the prompt and VALUE clauses. The last, optional parameter is the array boxes initially selected. The form in form-two.php asks, "What Operating Systems have you used? Check all that apply." The function is called with:

$OperatingSystemBoxes = MakeCheckBoxes(
  "OperatingSystem",array("Unix","Linux","Windows 2000","Windows 9x","Other"),
   $OperatingSystem);

Note that the last parameter is an array. Since the result of this check box group upon submitting the form is an array, the array variable can be used for the last parameter. If some items are to be initially selected, the last parameter might be:

array("Value1","Value2") ;

If only one box is to be generated, the call might look like:

$OneBox = MakeCheckBoxes("OneBox",array("Unix"),array("Unix"));

Error processing

The script contains a function CheckResults() to validate required fields. There is a series of statements checking form variables. A few error-checking statements are:

	if (!$FirstName) $ErrorArray[] = "No first name entered";
$OScount = count($OperatingSystem);
if (!$OScount) $ErrorArray[] = "Select at least one Operating System";

One element of array $ErrorArray is loaded for each error. The error process function may be used as a template to perform more ambitious checking.

Finally, the function returns 1 if there are errors present and 0 if there are no errors with the following:

  $ErrorCount = count($ErrorArray);
  return ($ErrorCount) ? $ErrorsPresent : $NoErrors ;

If there are errors, the function DisplayErrors() is called, passing the array containing the errors. After displaying a nicely formatted list of errors, the form is again displayed with previously entered values displayed.

A word of caution: Several of the functions in this script use the count() function on a passed array to determine the number of array elements. The developed count determines the number of required buttons or boxes and, hence, the number of iterations. Be careful if zero is passed as a parameter as count(array(1,1,1,1)); returns 4, but count(array(1,0,1,0)) returns 2.

Program flow

Writing a good program involves decomposing a problem into small logical units. When faced with subprogram code generation, give serious thought to generalizing the function to solve a class of problems, not just a specific problem.

As stated in the last segment, a good program, as opposed to a program that simply works, has these characteristics:

  1. It does what it is supposed to do, under all circumstances.
  2. It is easy to modify by someone other than the original programmer.
  3. It's efficient in the use of resources, both human and computer.

In keeping with these lofty goals, a good program should have a main line that calls subprograms to do all the hard work. The main body of the program should therefore just be logic components required to solve the task at hand. In form-two.php, the all-important main line is simply:

<?php

makehtmltop("Form Series","Example Two");

empty($firstpass) ?	# first pass if empty
  showform() :    	# display the form if first pass
  $errors = checkresults($errorarray); # check results if not first pass

($errors) ? # are there form processing errors?
  displayerrors($errorarray) : # display errors
  showresults();		  # display results

exit; # all done

Here's what happens. Put up an HTML top of page. If it's the first pass, display the form. If it's not the first pass, check to make sure all fields conform to validation rules.

If there were any errors, display the errors, and redisplay the form populated with any values from the previous submission. Show the final results if there are no errors.

So there you have it, fellow phanatics, a group of functions to make your form generation life easier. If you enhance any of these functions, I would very much like to hear about them.

As promised, we will take another look at the $globals array before closing this session. The new array dumper is yet another generalized function.

The $GLOBALS array revisited

As mentioned in our last episode, the $GLOBALS array contains environment variables, both GET and POST data from forms, cookie variables, and server variables. As such, the possibilities of naming conflicts are very real. If you have a cookie variable called $status and a form variable likewise named $status, what happens when you ask the real $status to stand up? The answer is, it depends.

The file php.ini has a variable called variables order. The default setting is:

variable_order  = "egpsc"

Array elements for $GLOBALS are loaded from left to right based upon the configuration setting in php.ini. If the initial installation default setting on your system is still intact, cookie variables would be loaded last and therefore have highest priority. Be especially alert for naming duality when enhancing third-party scripts and using objects.

You can be totally safe if you access the individual array components. If your form uses the POST method, its variables may be accessed using the HTTP_POST_VARS array. If the NAME parameter of a form field is FirstName and you use the GET method, the data is available in $HTTP_GET_VARS[FirstName].

Now back to my bug. The individual $GLOBALS array elements may themselves be arrays. The solution to correctly displaying an array of arrays is nested loops. Let's build an array dumping function, DumpArray(), for displaying any two-dimensional array. Here is the call and the function header:

	DumpArray("GLOBALS",$GLOBALS);

function DumpArray($ArrayName,&$Array)

The function required two parameters, the literal name of the array to be processed and the variable containing the array. When communicating with a subprogram, variables are passed in one of two ways: call by value and call by reference. When calling by value, a local copy of the original is established within the subprogram. Changes made to the local copy are not reflected in the original and die when the subprogram is exited. On the other hand, when calling by reference, a pointer to the original variable is passed, and any changes made to the variable in the subprogram are actually being made to the original variable.

Both methods have advantages. You may want to manipulate the local copy and not change the original. In this case, call by value. Conversely, the data structure to be passed to the subprogram may be large. If a large data structure is to be modified in a function and returned, it must be copied twice -- once when the function is called and again when it's returned.

In this function, I have chosen to call by reference, since the array to be displayed could be quite large. If you choose to call by reference, the syntax of either the calling or receiving parameter must start with an &. I like to have the subprograms parameter indicate the reference since it only has to be done once.

The heart of the new dumping function is:

foreach ($Array as $Key=>$Value){
  if (is_array($Value))

Source code

Source code for form-two.php

Source code for dump-globals.php

The passed array is traversed as before. Before displaying a component, it is tested by the is_array() function. If it's an array, an inner loop is started; if not, the element is displayed. You can see the output of a script that calls the DumpArray() function passing a reference to the $GLOBALS array by clicking here.

How about a three-, or more, dimensional array, you might reasonably ask. This would be a perfect spot for a function making recursive calls. (A recursive function is one that calls itself.) We will revisit the N-dimension array problem is a future episode.

Urb LeJeune is a 25-year programming veteran with over 10 years of Internet experience thrown in for good measure.


Read more PHP Forms columns.

Discuss this article in the O'Reilly Network PHP Forum.

Return to the PHP DevCenter.

 

Copyright © 2009 O'Reilly Media, Inc.