Team LiB
Previous Section Next Section

Form Processing

Anytime you are working with HTML forms, using some method or another you have to deal with processing that form. Often, not only does the form have to be processed (meaning you have to do something with the form data), but it almost always has to be validated in some fashion. In fact, it is strongly recommended that all form data is validated prior to being used in your scripts.

Basic Form Processing and Validation

In the simplest sense, form validation and processing is nothing more than working with the appropriate superglobal array ($_GET or $_POST) to do something in your PHP script. However, for a form of any complexity, often a considerable amount more goes into the validation of the data. As previously stated, it is simply bad practice (and dangerous) to use user-submitted data without properly validating it. For anything beyond the most elementary validation, usually all form validation is done via regular expressions, as shown in Listing 5.6:

Listing 5.6. Elementary Form Validation
<?php
     if(isset($_GET['submit'])) {
          if(preg_match("/^\(([2-9][0-9]{2})\)[2-9][0-9]{2}-[0-9]{4}$/i",
                                  $_GET['phone']) != 1) {
               echo "The phone field was invalid<BR>";
          }
     } else {
          /* Code to process form here */
     }
?>
<HTML>
<HEAD><TITLE>Elementary form validation</TITLE></HEAD>
<BODY>
<FORM ACTION="<?php echo $_SERVER['PHP_SELF']; ?>" METHOD=GET>
<INPUT TYPE="hidden" NAME="submit" VALUE="1">
Phone: <INPUT TYPE="text" NAME="phone" SIZE=13 MAXLENGTH=13>
(ex. (810)555-1212)<BR>
<INPUT TYPE="submit" VALUE="Send">
</FORM>
</HTML>

Because form validation is such an application-specific subject (every situation is different), there is little benefit to discussing more about the general validation and processing of forms. Instead, we'll try to kill multiple birds with one stone by creating a form-processing and validation architecture that is general enough to use on any form without sacrificing flexibility. Be forewarned that to create a processing script that is both flexible and easy to use requires some fancy PHP programming! Don't worry too much, however; I will be explaining the script extensively.

General-Purpose Form Validation

Before I actually discuss a line of code, let me first explain the concept behind this all-purpose form-validation script. When I set out to create this script, I had the following goals in mind:

  • Be flexible enough to validate and process any form data.

  • Encourage the separation of validation-related code from presentation-related code.

  • Most of all, be easy enough to use when implementing any type of HTML form.

To accomplish all three of these goals, it took a little planning (all solid scripts do); however, in the end I think you'll agree that all three goals are met!

The form processor works through a combination of hidden form elements and dynamic function calls (discussed in Chapter 1, "Basic PHP Development"). These hidden form elements will be used by the PHP script to both provide a human-friendly description of each field element (in case of an error) and identify those form fields that are "required." Specifically, for any given form element with a name of <name>, the description of that field is defined as being stored in a hidden element by the name of <name>_desc. The example that follows defines a text field named "phone" with an extra description field for use within our script:

<INPUT TYPE="text" NAME="myphone">
<INPUT TYPE="hidden" NAME="myphone_desc" VALUE="Phone Number">

The second hidden form element that the form-processing script uses is called required and should contain a comma-separated list of required elements. For instance, if you have three required elements whose NAME attributes are phone, email, and fax, the hidden required tag would be as follows:

<INPUT TYPE="hidden" NAME="required" VALUE="phone,email,fax">

Although not a strict requirement, the VALUE attribute of each visible element should be populated with its associated value in the appropriate superglobal (that is, $_GET['myvar']) if available. This is done so that if the form is submitted and not processed for whatever reason (for example, an error) the user will not have to retype everything.

The next issue to be tackled is how to deal with validation errors that may occur when the form is submitted. In the form validation script, this is handled through two global PHP variables: $form_errors and $form_errorlist. When the form validation script attempts to validate the data submitted to it, upon an error, it creates these two variables. The first variable $form_errors is a Boolean value indicating whether an error occurred during validate, and the second $form_errorlist is an array of error messages that occurred during the form validation. How these variables are used in your script to display validation errors to the user is subjective; however, one recommended method is as follows:

<?php if($form_errors): /* An error occurred processing the form */ ?>
<UL>
<?php foreach($form_errorlist as $val): ?>
<LI><?php echo $val; ?>
<?php endforeach; ?>
</UL>
<?php endif; ?>

By placing this immediately prior to the form, the result will be a nicely formatted bulleted list of every validation error that occurred.

A complete example of an HTML form that is used with our form validator is shown in Listing 5.7:

Listing 5.7. Form Example for the Form Validator Script
<?php if($form_errors): /* An error occurred processing the form */ ?>
<UL>
<?php foreach($form_errorlist as $val): ?>
<LI><?php echo $val;?>
<?php endforeach; ?>
</UL>
<?php endif; ?>

Please fill out the following form (* = Required)<BR>
<FORM ACTION="<?php echo $_SERVER['PHP_SELF']; ?>" METHOD=GET>
<INPUT TYPE="hidden" NAME="submit" VALUE="1">
<INPUT TYPE="hidden" NAME="required" VALUE="phone,email,fax">
<INPUT TYPE="hidden" NAME="phone_desc" VALUE="Phone Number">
<INPUT TYPE="hidden" NAME="email_desc" VALUE="Email Address">
<INPUT TYPE="hidden" NAME="fax_desc" VALUE="Fax Number">
Your Name: <INPUT TYPE="text" NAME="name"><BR>
* Your Phone Number:
<INPUT TYPE="text" NAME="phone" VALUE="<?php echo $_GET['phone']; ?>"><BR>
* Your Email Address:
<INPUT TYPE="text" NAME="email" VALUE="<?php echo $_GET['email']; ?>"><BR>
* Your Fax Number:
<INPUT TYPE="text" NAME="fax" VALUE="<?php echo $_GET['fax']; ?>"><BR>
<INPUT TYPE="submit" VALUE="Send">
</FORM>

Now that you have an idea of what a form to be used with the form validation script looks like, it's time to move on to the actual PHP script that will process the form. The form validation script is broken up into three separate functions: add_error(), _process_form(), and validate_form(). Of these three functions, validate_form() does the bulk of the work in validating the form data, and add_error() and _process_form() serve as support functions.

As I've already mentioned, validation errors that occur in the form validation script are recorded through the $form_errors and $form_errorlist variables. The add_error() function, as its name implies, is used to manipulate these two variables. Because this function is quite simple, displaying the function should be sufficient for an explanation (see Listing 5.8):

Listing 5.8. The add_error()_Function
<?php
    $form_errors = array();
    $form_errorlist = false;

    function add_error($error) {
        global $form_errorlist, $form_errors;
        $form_errorlist = true;
        $form_errors[] = $error;
    }
?>

The meat of the form validation script is in the validate_form() function. This function takes a single parameter (a reference to the superglobal array to validate). When executed, this function attempts to perform a number of tasks in the interest of validating the data. During the course of this function, if any validation errors occur, validate_form() calls the add_error() function with an appropriate error message (hence populating the error variables). When executed, the validate_form() starts by first processing the required hidden field and checks to make sure that all required fields are not empty. Following this check, the validate_form() attempts to process each individual form element according to the following rules:

  • If the element is named submit, required, or ends in _desc, it is ignored.

  • For all other elements, validate_form() attempts to call the function <name>_validate() (where <name> is the name of the current element).

Unless defined by the user, the <name>_validate() functions do not exist. These functions are your responsibility to create to validate each individual form element (or at least the elements you are concerned with validating). These functions should accept two parameters (the value submitted and a description of the field taken from the <name>_desc element) and should return true if the submitted value is valid or return an error message upon failure. For example, if you were validating a form element whose NAME attribute is phone (a phone number), the following function should be defined to validate that data (see Listing 5.9):

Listing 5.9. A Sample Form Element Validation Function
<?php
    function phone_validate($data, $desc) {
        $regex = "/^\([2-9][0-9]{2}\)[2-9][0-9]{2}-[0-9]{4}/i";
        if(preg_match($regex, $data) != 1) {
            return "The '$desc' field isn't valid!";
        }
        return true;
    }
?>

Assuming that each validate_form() executes and does not encounter any errors, it then calls the _process_form() function. This function is designed to clean up any nonrequired form elements (the _desc, submit, and required hidden elements) and call the function process_form(). As with the validation functions just discussed, the process_form() function must be defined by you and is designed to allow you to actually perform whatever action was desired after a successful validation. It accepts a single parameter (an array of the submitted data) and has no return value. If this function does not exist, nothing will be done with the submitted data upon a successful validation. A sample process_form() function is provided in Listing 5.10, which emails the contents of the form:

Listing 5.10. A sample process_form() Function
<?php
    function process_form($data) {

        $msg = "The form at {$_SERVER['PHP_SELF']}
                was submitted with these values: \n\n";
        foreach($data as $key=>$val) {
            $msg .= "$key => $val\n";
        }
        mail("joeuser@somewhere.com", "form submission", $msg);

    }
?>

Because the validate_form() function itself is best explained in conjunction with the other required functions, I will not attempt to explain the function further in text. Rather, see Listing 5.11, which contains a fully commented validate_form() function as a part of the complete form-validation script:

Listing 5.11. The Complete Form Validation Script
<?php

    /********** BEGIN FORM VALIDATION SCRIPT ***********/
    $form_errors = array();
    $form_errorlist = false;

    function add_error($error) {
        global $form_errorlist, $form_errors;
        $form_errorlist = true;
        $form_errors[] = $error;
    }

    function _process_form($method) {

        /** This function is called by the validate_form() function only! */

        /* Check to see if the process_form() function exists. If this
           function doesn't exist, there is no need to bother with cleaning
           up the form data. */
        if(function_exists("process_form")) {

            /* Make a copy of the submission data and iterate through it
               removing any elements that aren't part of the actual
               submission from the copy. */
            $data = $method;
            foreach($data as $key=>$val) {

                if(preg_match("/(submit|required)|(_desc$)/i", $key) == 1)
                    unset($data[$key]);
            }

            /* Call the process_form() function and pass it the cleaned
               up version of the form submission */
            process_form($data);
        }
    }

    function validate_form($method) {

        /* This variable is used to determine if any validation
           errors occurred during the course of the function.
           By default, we assume the form is valid */
        $process = true;

        /* Check for the existence of the 'required' form element.
           If this element does not exist the form is automatically
           invalid. */
        if(!isset($method['required'])) {

           add_error("Required hidden element 'required' missing!");
           $process = false;
        } else {

            /* Parse out the required field elements from the
               'required' form element and store them in an array*/
            $required = explode(',',$method['required']);

            /* Check to ensure each required element exists, and
               at least has some sort of data (not empty) */
            foreach($required as $val) {
                if(empty($method[$val])) {

                    /* This particular element should have some data,
                       but for some reason is empty. Hence, attempt to
                       get the human-friendly description of the element
                       and display an error to the user. If no human-friendly
                       description was provided use the element name instead */
                    if(isset($method[$val."_desc"])) {
                        $errormsg = "The required field '" . $method[$val."_desc"] .
                                    "' was empty!";
                    } else {
                        $errormsg = "The required field '$val' was empty!";
                    }
                    add_error($errormsg);
                    $process = false;
                }
            }

            /* Begin the iteration through all of the form elements */
            foreach($method as $key=>$val) {

                /* Because we are only concerned with validating the actual
                   form elements the user is editing, only check elements
                   that are not named 'submit', 'required' or end in '_desc' */
                if(preg_match("/(submit|required)|(_desc$)/i", $key) != 1) {

                    /* Construct the function name that will be called to
                       validate the data */
                    $func = $key."_validate";

                    /* Check to see if the validation function exists for this
                       form element. */
                    if(function_exists($func)) {

                        /* Since the validation function exists for this
                           element, call it passing it the value of the element
                           and the human-friendly description (if available) */
                        if(!isset($method[$key."_desc"])) {
                            $result = $func($val, $key);
                        } else {
                            $result = $func($val, $method[$key."_desc"]);
                        }

                        /* If the validation function does not return true,
                          then the form element is not valid and $return should
                          contain an error message. Add the error message to
                          the list of errors which occurred. */

                        if($result !== true) {
                            add_error($result);
                            $process = false;
                        }
                    }
                }
            }
        }

        /* Assuming no validation errors occurred, $process
           should still be true. If it is, call the _process_form()
           function and pass it the validated data and end the
           function by returning true. */
        if($process) {
            _process_form($method);
            return true;
        }

        /* Something went wrong in the validation, return false */
        return false;
    }

    /********** END FORM VALIDATION SCRIPT ***********/
    /********** BEGIN USER-DEFINED SCRIPT ***********/

    /* This is just a nicety. By only using $method
       any time we want to access the superglobal data
       we can quickly change the submission method from
       GET to POST (or the other way around) without
       changing multiple values. */

    $method = &$_GET;

    /* Check to see if the form was submitted, if so
       begin the validation process */
    if(isset($method['submit'])) {
        validate_form($method);
    }

    /* This function is called by validate_form() to
       validate the form element whose name is 'email'. */

    function email_validate($data, $desc) {
        $regex = "/^[a-z0-9\._-]+@+[a-z0-9\._-]+\.+[a-z]{2,3}$/i";
        if(preg_match($regex, $data) != 1)
            return "The '$desc' field is invalid.";

        return true;
    }

    /* This function is called by validate_form() upon successful
       validation of the form. */
    function process_form($data) {

      $msg = "The form at {$_SERVER['PHP_SELF']} " .
             "was submitted with these values: \n\n";
      foreach($data as $key=>$val) {
          $msg .= "$key => $val\n";
      }
      mail("joeuser@somewhere.com", "form submission", $msg);

    }
    /********** END USER-DEFINED SCRIPT ***********/

?>
<HTML>
<HEAD><TITLE>Form Validation Example</TITLE></HEAD>
<BODY>
<?php
    /* Display any errors that occurred during validation */

    if($form_errorlist): ?>
    Please correct the following errors:<BR>
    <UL>
    <?php foreach($form_errors as $val): ?>
    <LI><?=$val?>
    <?php endforeach; ?>
    </UL>
<?php endif; ?>
<FORM ACTION="<?php echo $_SERVER['PHP_SELF']; ?>" METHOD=GET>
<INPUT TYPE="hidden" NAME="required" VALUE="first,last,email">
<INPUT TYPE="hidden" NAME="submit" VALUE="1">
<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0>
<TR>
    <TD COLSPAN=2>Please fill out the following fields. (* = Required)</TD>
</TR>
<TR>
    <TD>*First Name:</TD>
    <TD><INPUT TYPE="text" NAME="first"
               VALUE="<?php echo @$method['first']; ?>">
        <INPUT TYPE="hidden" NAME="first_desc" VALUE="First name"></TD>
</TR>
<TR>
    <TD>*Last Name:</TD>
    <TD><INPUT TYPE="text" NAME="last" VALUE="<?php echo @$method['last']; ?>">
        <INPUT TYPE="hidden" NAME="last_desc" VALUE="Last name"></TD>
</TR>
<TR>
    <TD>Phone Number:</TD>
    <TD><INPUT TYPE="text" NAME="phone"
               VALUE="<?php echo @$method['phone']; ?>">
        <INPUT TYPE="hidden" NAME="phone_desc" VALUE="Phone number"></TD>
</TR>
<TR>
    <TD>*E-mail:</TD>
    <TD><INPUT TYPE="text" NAME="email"
               VALUE="<?php echo @$method['email']; ?>">
        <INPUT TYPE="hidden" NAME="email_desc" VALUE="E-mail address"></TD>
</TR>
<TR>
    <TD COLSPAN=2><INPUT TYPE="submit" VALUE="Send"></TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>

Separation of Presentation from Validation

You may have noticed in Listing 5.11 that I made it a point to use comments to define where the "user-defined" and form-validation sections began and ended. As I mentioned previously, one of the goals of the form-validation script is to separate presentation code from validation code. Looking at Listing 5.11, you may notice that this script can actually be divided into three separate files: one for the HTML form itself, one for the user-defined validation functionality, and a third for the form-validation script.

For argument's sake, let's assume everything between the form-validation script comment markers is in the file formvalidate.php and the HTML form is in the file htmlform.php. By placing the remainder of the code in a third file with a few includes, you would have something resembling Listing 5.12 (comments removed for the sake of space):

Listing 5.12. Separating the HTML and Validation Code
<?php

    include_once('formvalidate.php');

    $method = &$_GET;

    if(isset($method['submit'])) {
        validate_form($method);
    }

    function email_validate($data, $desc) {
        $regex = "/^[a-z0-9\._-]+@+[a-z0-9\._-]+\.+[a-z]{2,3}$/i";
        if(preg_match($regex, $data) != 1)
            return "The '$desc' field is invalid.";

        return true;
    }

    function process_form($data) {

      $msg = "The form at {$_SERVER['PHP_SELF']}" .
             " was submitted with these values: \n\n";
      foreach($data as $key=>$val) {
          $msg .= "$key => $val\n";
      }
      mail("joeuser@somewhere.com", "form submission", $msg);

    }

    include("htmlform.php");

?>

Clearly, separating the script into multiple files has made it much more manageable. Furthermore it can be applied to any HTML form simply by including formvalidate.php at the beginning of the script, defining the necessary validation and process functions, and including the HTML form at the end of the script! This type of form validation is ideal, and you are encouraged to use the form validation script I have developed for this chapter (or your own similar facility) in your own scripts.

    Team LiB
    Previous Section Next Section