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


PHP Forms

Phorm Manager

01/25/2001

Previously in PHP Phanatics:

The Universal Web Form Processor

PHP With HTML Forms

Using PHP to Interpret Forms

In the last article, the Phanatic was leading an expedition into a largely uncharted area. The conceptual project was a generalized form handler. The task's starting point was Matt Wright's famous "formmail" and included adding many more bells and whistles. While putting together the last tome, time and space were closing in on us, and in an effort not to strain the Phanatic's rapidly aging brain, we pulled into shore for a rest. Let's review the project's design goals before setting out to sea again.

Design goals

The desirable conceptual characteristics of the form handler and the developed script should, based upon the form designer's choice:

Some of the task's more challenging programming considerations are:

Some of these issues were stowed away in the last article, so you might want to review that tome before continuing.

The Phanatic is a little late with this column because, as usual, things take longer than estimated, especially with a Herculean task like this one. This is a major script and as such deserves a special name. After burning some midnight oil and injecting enough sugar to kill a small colony of rodents, we have it: "The Phorm Manager," possibly a little grandiose, but what's the point of being a Phanatic if one is not somewhat pompous.

The Swiss Army chainsaw

The Phanatic has such low self-esteem that he wants to be all things to all people. As a consequence, this project is about to emerge (degenerate?) into a Swiss Army chainsaw. You may not want all of the Phorm Manager's features in your application, so feel free to take out what you think is the Phanatic's application candy.

The Phorm Manager, being somewhat schizophrenic, can cope with different missions. There are three task handlers, namely:

Forms, especially those of the complicated persuasion, require a good bit of development work. It is frequently useful to test the form before placing it in production. A little cosmetic tweaking is somewhat like chicken soup when you're sick: It may not help but it can't hurt.

System demonstration

Before getting too involved in the inner workings of the Phorm Manager, it might help your understanding if you play with the demo for a bit. The demo form and processing script are designed to show off some of the Manager's features. If you enter your own e-mail address, you will get an e-mail copy of the finished product. There are a pair of radio buttons allowing switching modes between Test and Mail. Test mode displays the input data as well as the data help in the Form, System, and Environment arrays.

In addition, the first and last names will not allow null input. The e-mail address and ZIP code will be validated for correct format. The test numeric will pass only numeric data. More on these further on in this column; suffice to say, the form designer can determine which, if any, fields are to be non-null, e-mail addresses, ZIP codes, or numeric.

One feature used in the demo form requires some additional discussion so you can use the concept in your future form use. A PHP array can be populated with an explicit subscript such as:

$A[] = "abc";
$A[] = "xyz";

and so on. If a form variable can contains multiple vales and will subsequently be processed by a PHP script, the following code may be used:

Unix <INPUT TYPE="CHECKBOX" NAME="OperatingSystem[]" VALUE="Unix">
Linux <INPUT TYPE="CHECKBOX" NAME="OperatingSystem[]" VALUE="Linux">
Windows <INPUT TYPE="CHECKBOX" NAME="OperatingSystem[]" VALUE="Windows">
Other <INPUT TYPE="CHECKBOX" NAME="OperatingSystem[]" VALUE="Other">

The GetFormData converts the array to a comma-separated list with the following:

foreach ($FormVariables as $Name=>$Value) {
if (is_array($Value)) $FormVariables[$Name] = join(",",$Value);

One note of caution: If all items are checked, $OperatingSystem[0] contains "Unix" and $OperatingSystem[3] contains "Other." However, if only "Windows" is checked, it will be $OperatingSystem[0], and $OperatingSystem[1..3] are undefined.

So, take a break and go to our demo here.(Editor note: For anti-spam reasons, we can not allow the e-mail to actually get sent, so you will see an error message. )

PHP mail

Let's ease into this session with a look at PHP's mail() function. Firstly, mail() is configured to use sendmail on a Unix box, and on other systems it will look for a local or remote mail server. If you have problems with mail, start by checking the SMTP directive in the php.ini file.

The basic syntax of the mail function is:

mail(string to, string subject, string message[, string additional headers]);

A simple mail() call, with apologies to Elizabeth Barrett Browning, might be:

mail("urb@usats.com","The Subject is Love PHP",
"My message is: How much do I love PHP? Let me count the ways");

Note, a From: address is not required. The optional fourth parameter is a list of headers, each of which must be terminated with the CRLF (Carriage Return Line Feed) characters \r\n. Here is an example using both From: and Reply-to: headers.

mail($ToAddress,$Subject,$MailMessage, 
    "From: $FromAddress\r\nReply-to:$ReplyAddress");

We'll return to mail shortly.

Internal data management

The Phorm Manager has two major conceptual arrays, $FormVariables and $SystemVariables. These arrays comprise the brain and nervous system of the Phorm Manager. Care must be exercised when designing your form. The difference between a system variable, a user form variable, or an environment variable can be as simple as one mistyped character. The test suite displays the names and contents of the form variables, the system (hidden field) variables, and environment variables without sending mail.

The heartbeat of this application is a series of hidden form fields. The hidden fields supply control and cosmetic information, as opposed to user-supplied data, for the Phorm Manager. There is only one required hidden field, the e-mail address to receive the e-mailed data. A typical form entry might be:

<INPUT TYPE="HIDDEN" NAME="Recipient" VALUE="urb@usats.com">

Let's take a look at how FormVariables and SystemVariables come into being and how they interrelate.

Program flow

The design of a good program involves breaking the task's specification into smaller and smaller logical parts. Computer science theorists like to call this process decomposition or partitioning. When the parts cannot be broken into smaller logical pieces, you have a blueprint for a coding solution. In essence:

Sub_Program_One();
Sub_Program_Two();

Sub_Program_N();

Walk through the logic step by step. When you can say if Sub_Program_One does its job and Sub_Program_Two does its job, and so on, the logic is completed. When all the individual subprograms are successfully coded and tested, the job is done. The top of your program should, to a practical degree, be a logic road map with calls to subprograms and a minimal amount of individual executable statements. This technique avoids the "spaghetti code" characterizing so much poor programming. If you follow this paradigm, the Phanatic will be proud of you and the people (including you) who will modify your program in the future will be very grateful.

Good programing design and coding style transcend any specific language. Try and remember our goals: Effectiveness, Maintainability, and Efficiency.

The first series of the Phorm Manager's function calls are:

StartUp($SystemVariables,$Action);
GetFormData($Method, $FormVariables);
FixArrays($SystemVariables,$FormVariables,$LongestName);
MakeHTMLtop($HTML,$SystemVariables);
LegalDomain($HTML,$AcceptableDomains,$SystemVariables);

The application's first subprogram call is to StartUp(). This function loads the SystemVariables with allowable fields and, in some cases, default values. A few lines are:

$SystemVariables = array(
    formaction=>"M",
    allownamealias=>True,  # Allow name aliasing
    recipient=>"",  # E-mail recipient - only required hidden field
    sendacknowledgement=>"",  # Send submitter an acknowledge
    subject=>"Form Submission",  # E-mail subject   

  # Cosmetic properties. The first six are for the <BODY> tag.
    bgcolor=>"WHITE",  # Background color
    linkcolor=>"BLUE",  # Link color
    vlinkcolor=>"RED",  # Visited link color
    textcolor=>"BLACK",  # Text Color
    alinkcolor=>"GREEN",  # Active Link Color
    background=>"",  # Background graphic

The $SystemVariables associate array defines all allowable "name" control and cosmetic properties. In addition, default values are established in some cases. To illustrate, the default display page default background color in the above initialization is WHITE. If there is no hidden field named bgcolor with a valid Name clause, white will be used. However, if there is a value passed to the Manager, it will override the default value.

Allowances are made from various naming preferences in the form's hidden field NAME clause. Then all spaces, dashes, and underscores are taken out and the supplied names are changed to all lower case. The following NAME clauses will all be equivalent: FirstName, firstname, FIRSTNAME, First Name, first_name, firstNAME, and any variation of this theme.

The next program call is to GetFormData(). This function starts by determining if a GET or POST method was used when submitting the form. If a POST method was used, an array named $HTTP_POST_VARS is loaded, and $HTTP_GET_VARS is loaded if a GET method was used. The appropriate array is transferred to the $FormVariables array, so there is no additional testing for submission method required.

Next, on the hit parade is a call to FixArrays(). This function loops through the $FormVariables array. When a form is submitted, there is no way to determine if a particular name/value pair started life as a hidden field or was entered by the Web surfer. While looping through the array, the converted name value is checked against the $SystemVariables name properties. If a match is found, the value is transferred to the $SystemVariables array and removed from the $FormVariables array. At the end of this function, the $SystemVariables array contains the default control variables plus any information from hidden fields that override the default values. The $FormVariables array contains only name/value pairs pertaining to user inputs. Pretty clever even for the Phanatic!

MakeHTMLtop() simply generates the common top-of-page HTML. Modify this function to somewhat change the application's look and feel.

The call to LegalDomain() was added since the previous column. If you don't control who can use your Phorm Manager, anyone can use it as a gateway. This is probably not a good idea. The program starts with the initialization of the $AcceptableDomains array. Something like:

$AcceptableDomains = 
       array("localhost","phpphanatic.com","iloveac.com");

The LegalDomain() function uses the environment variable $HTTP_REFERER, coupled with the parse_url() function, to extract the domain of the caller. If the domain is not in the list, a warning message is issued and processing is terminated. The statements:

  $URLparts = parse_url($HTTP_REFERER);
  $Domain = strtolower($URLparts[host]);

are followed by a traversal of the $AcceptableDomains array.

Do me a test

The Phorm Manager's task management is controlled by, what else, a hidden field named "formaction," or "FormAction" if you prefer.

<!-- FormAction can be Test or Mail, with Mail being the default -->
<INPUT TYPE="HIDDEN" NAME="FormAction" VALUE="Test">

Only the first letter is needed in the FormAction value clause, but we can keep the shortcut a secret. Set this variable's value to T to test the form without actually e-mailing anything.

Switch hitter

We'll use a PHP switch structure to control program flow.

The application's traffic cop is the switch statement. The switch construct falls into the class of PHP statements called "syntactic candy." They don't provide anything you couldn't do without them, but they make programming easier, cleaner, and more self-documenting. The ternary condition the Phanatic loves is another example of syntactic candy.

There is no equivalent to PHP's switch statement in Perl -- unfortunately -- but there is one in C. Here is the switch road map:

switch ($Action) {
case "M": # Action is to mail results to the recipient
# Check for data entry errors
Check4Errors($SystemVariables,$FormVariables,$Errors);
if ($Errors) { # If there were errors, process them.
ProcessErrors($HTML,$Errors);
break;
} # End of if ($Errors)
SendEmail($HTML,$Result,$SystemVariables,$FormVariables,
$AcknowledgementSent,$LongestName);
$Redirect = $SystemVariables[redirect];
if ($Redirect) {
header("Location: $Redirect");
exit;
} else {
if ($Result)
$HTML.="Mail Successfully Sent to $SystemVariables[recipient]\n";
if ($AcknowledgementSent)
$HTML.="<BR>Acknowledgement Successfully Sent to $AcknowledgementSent\n";
$HTML.="<P>The following are the name/value pairs of data submitted\n";
DisplayArrayVariables($FormVariables,$HTML);
}
break;
case "T": # Action is Test form
# Check for data entry errors
Check4Errors($SystemVariables,$FormVariables,$Errors);
if ($Errors) { # If there were errors, process them.
ProcessErrors($HTML,$Errors);
} # End of if ($Errors)
$HTML .= "<H3><FONT COLOR=\"RED\">Form Variables</FONT>\n";
$HTML .= "submitted using the \"$Method\" method.</H3>\n";
DisplayArrayVariables($FormVariables,$HTML); #$Display variables);
$HTML .= "<H3><FONT COLOR=\"RED\">System Variables</FONT></H3>\n";
DisplayArrayVariables($SystemVariables,$HTML); #$Display variables);
$HTML .= "<H3><FONT COLOR=\"RED\">Environment Variables</FONT></H3>\n";
DisplayArrayVariables($HTTP_ENV_VARS,$HTML); #$Display variables);
break;
default: # In case there is no "M" or "T"
$HTML .= "<CENTER><H2>Invalid Action</H2></CENTER>\n";
} # End of switch ($Action)

Finally followed by:

MakeHTMLbottom($HTML,$SystemVariables);
print $HTML;
exit;

If you understand the logic of a handful of lines, you understand what makes this 400+ line script work.

The HTML code is accumulated in the variable $HTML, which is passed by reference to the function adding HTML. Displaying the variable once at the end of the program saves a great deal of I/O overhead.

Program control using hidden fields

A template is a good place to start when designing a new form to be processed by the Phorm Manager. Here is a link to a typical template.

The template contains the following hidden fields. The comment line about the hidden field, or group, should be self-explanatory.

<!-- Recipient receives emailed from variable and is only required field. -->
<INPUT TYPE="HIDDEN" NAME="Recipient" VALUE="urb@usats.com">

<!-- To send an email acknowledgement to submitter the hidden field -->
<!-- SendAcknowledgement must be set to "Yes". In addition, the submitter's -->
<!-- email address must be entered in a field name "UserEmail", case matters.-->
<INPUT TYPE="HIDDEN" NAME="SendAcknowledgement" VALUE="Yes">

<!-- A comma separated list of all fields that may not have null values. -->
<INPUT TYPE="HIDDEN" NAME="Required" VALUE="FirstName,LastName">

<!-- A comma separated list of all fields requiring email address validation.-->
<INPUT TYPE="HIDDEN" NAME="CheckEmail" VALUE="TestEmailAddress">

<!-- A comma separated list of fields that must contain numeric data. -->
<INPUT TYPE="HIDDEN" NAME="CheckNumeric" VALUE="NumberField">

<!-- A comma separated list of fields that must contain valid zip codes. -->
<INPUT TYPE="HIDDEN" NAME="CheckZipCode" VALUE="ZipCode">

<!-- FormAction can be Test, or Mail. Mail is the default value. -->
<INPUT TYPE="HIDDEN" NAME="FormAction" VALUE="Mail">

<!-- The following four optional parameters are used in the body tag. -->
<INPUT TYPE="HIDDEN" NAME="TextColor" VALUE="BLUE">
<INPUT TYPE="HIDDEN" NAME="BGcolor" VALUE="WHITE">
<INPUT TYPE="HIDDEN" NAME="LinkColor" VALUE="GREEN">
<INPUT TYPE="HIDDEN" NAME="VlinkColor" VALUE="RED">
<!-- The following two optional parameters are for the bottom of page link. -->
<INPUT TYPE="HIDDEN" NAME="ReturnLinkURL" VALUE="http://oreilly.com">
<INPUT TYPE="HIDDEN" NAME="ReturnLinkTitle"
VALUE="The O'Reilly Home Page">
<!-- Transition to the following URL if there are no error. -->
<INPUT TYPE="HIDDEN" NAME="Redirect" VALUE="">
<!-Heading names must begin with a "H" and be followed only by numbers -->
<INPUT TYPE="HIDDEN" NAME="H01"
VALUE="==================== Phorm Manager ====================">

The headings require some additional explanation. Sometimes, especially with long forms, it's desirable to have section headings. When the submitted fields are displayed, both on the screen and in the e-mail, the fields are listed in the order they were displayed on the form page, from left to right and top to bottom. Any heading fields must therefore be inserted in the appropriate place. You can see the effect of heading fields in the demo form. The system determines heading fields by their name. The first character in the NAME clause must be an H, either upper or lower case. This letter must be immediately be followed by between one and three numeric digits.

Validation

The Phorm Manager optionally performs four types of validation. The most frequently used is simply to prevent a null input in a specific field. The hidden field Required contains a comma-separated list of field names to be checked. An example would be:

<INPUT TYPE="HIDDEN" NAME="Required" VALUE="FirstName,LastName">

The form variables FirstName and LastName will not be processed unless they both have a non-null value.

A second type of validation is e-mail addresses. As a practical matter, e-mail addresses cannot be checked in real time. A mail server may be temporarily down or unreachable. Failure to contact a remote mail server doesn't mean the address is invalid. What the function ValidateEmail() does is use a regular expression to determine if the supplied address is in the correct format. Although interposed characters will not be detected, it will catch the majority of common errors like a missing @ or invalid characters. Again, a comma-delimited list contained in a hidden field named "CheckEmail" determines which of the form's fields are to be validated.

<INPUT TYPE="HIDDEN" NAME="CheckEmail" VALUE="TestEmailAddress">

The third type of validation is US ZIP code. Again, the input is only checked for valid formatting. There are many potential but unused ZIP codes that would be passed by the ValidateZipCode() function. However, the function will check for either 5-digit or 9-digit ZIP codes. Designated fields requiring validation are passed as a comma-delimited list in a hidden field named CheckZipCode.

The last validation uses the function CheckNumeric() to validate numeric fields. It will pass either integer or floating-point numbers but will not pass numbers with formatting such as 1,234.66. Once again, a comma-delimited list contained in a hidden field named CheckNumeric will cause numeric validation of these fields.

Sample Scripts

Demonstration Form/Script

Form Template

Phorm Manager Source Code

References

NuSphere

PHP.net

NuSphere

In the last article, the Phanatic introduced NuSphere. This easy-to-install package contains Apache, PHP, Perl, and MySQL. Having all of this functionality on my PC saved an immense amount of time while developing this application. There was no need to keep uploading files to a web site, testing, and repeating the process. All that was required was to edit, save, and run. Check the last column for a complete description and installation details.

If you want to use PHP to send mail from your PC, you have to configure an SMTP server. In the path C:\nusphere\apache\php (assuming you took the default path when installing) is a file named php.ini. Edit the following line with your mail server.

SMTP = mail-server-host-and-domain-name;

Summary

Related Reading

PHP Pocket ReferencePHP Pocket Reference
By Rasmus Lerdorf
Table of Contents
Sample Section
Full Description
Read Online -- Safari

So there we have it, PHP fans. A generic form processor with more bells and whistles than the Trans Siberian Express. It has been a challenging and fun project. The Phanatic aims to please even though there are times when he is not a good shot.

If you would add any enhancements, or would like to see some enhancements, or just have some comments, drop me a note. As usual, please let me know what types of things you would like to see in future articles.

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.