Generating and Validating Web Forms With PEAR HTML_QuickForm

November 12, 2007

Tutorials

Trust No One

I’m going to start off with a confession: when it comes to processing user input through a Web form, I tend to usually be found dangling my feet over the edge of the “extremely paranoid” end of the scale. It’s not that I’m a naturally distrustful person; it’s just that after pulling a couple of nasty all-nighters fixing a database that had been turned into alphabet soup by a couple of wayward application scripts, the importance of verifying user-supplied data before processing or saving it is forever branded into my consciousness.

I might be exaggerating a little bit, but the fact remains that if you’re a serious Web developer, you can’t ignore the importance of input validation in a Web application. Such input verification is one of the most important safeguards you can build into your application, and failing to do this can result in various nasty side-effects, ranging from records that are invalid or missing critical information to users gaining unauthorized access to sensitive data through SQL hacks and injection attacks.

That’s where this article comes in. Over the next few pages, I’ll be introducing you to one of PEAR’s most powerful tools for generating Web forms and validating the input that arrives through them: the HTML_QuickForm package. This package provides a flexible, reusable library of methods that can literally save you hours of time when dealing with form-based user input – and best of all, it’s free and extremely easy to use. So what are you waiting for? Jump right in, and let’s get going!

Out With The Old…

This tutorial makes a couple of assumptions. First, it assumes that you know the basic ingredients that go into cooking a Web form, and you have at least a passing familiarity with using PHP to process form input. Second, it assumes that you have an Apache/PHP/MySQL development environment already set up, and that you’ve managed to successfully install the HTML_QuickForm package, as well as its dependencies. For your reference, the HTML_QuickForm package is freely available from http://pear.php.net/package/HTML_QuickForm, and is currently maintained by Bertrand Mansion and Alexey Borzov.

I should also mention at this point that there exists a revision of HTML_QuickForm named HTML_QuickForm2 (http://pear.php.net/package/HTML_QuickForm2), which has been rewritten specifically for PHP 5.x. This tutorial, however, uses the original version of the package, mostly because the revision is still in early alpha development while the original HTML_QuickForm package is stable on both PHP 4.x and PHP 5.x platforms.

With all the caveats out of the way, let’s get started with a simple example. My first example is more in the nature of a big-picture view of what’s to come: I’ll show you one of the most common non-HTML_QuickForm way of generating and validating a Web form, and then revise the example to use HTML_QuickForm. This will not only give you an opportunity to admire the clarity, the composition and the exuberant feline beauty of a Web form generated through HTML_QuickForm, but it will also get you up and running with the package quickly. So, without further ado, here goes:

<html>
<head></head>
<body>
  <?php 
  if (!isset($_POST['submit'])) {
  // no POST submission, display form
  ?>
    <form method="post" action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>">
      <table>
        <tr>
          <td>Item name:</td> 
          <td><input type="text" name="name" size="30" /></td> 
        </tr>
                  
        <tr>
          <td>Item quantity:</td> 
          <td><input type="text" name="qty" size="3" /></td> 
        </tr>
        
        <tr>
          <td colspan="2"><input type="submit" name="submit" value="Submit" /></td> 
        </tr>
      </table>
    </form>
  <?php
  } else {
    // POST submission, validate input
    if (trim($_POST['name']) == '') {
      die('ERROR: Missing value - Item name');
    }
    if (trim($_POST['qty']) == '') {
      die('ERROR: Missing value - Item quantity');
    }
    if ($_POST['qty'] <= 0) {
      die('ERROR: Invalid value - Item quantity');
    }
      
    // process input
    // eg: save to database
    // attempt a connection
    try {
       $pdo = new PDO('mysql:dbname=test;host=localhost', 'user', 'pass');
    } catch (PDOException $e) {
       die("ERROR: Cannot connect: " . $e->getMessage());
    }
    
    // create and execute INSERT query
    $name = $pdo->quote($_POST['name']);
    $qty = $pdo->quote($_POST['qty']);    
    $sql = "INSERT INTO shoppinglist (name, qty) VALUES ($name, $qty)";
    $pdo->exec($sql) or die("ERROR: " . implode(":", $pdo->errorInfo()));
    
    // close connection
    unset($pdo);
        
    // display success message
    echo 'Thank you for your submission';
  }
  ?>   
</body>
</html>

At some point, every PHP developer has written a script that goes something like the one above. For those new to the game, this script isn’t particularly hard to understand. It’s divided into two segments, with the appropriate segment activated depending on a conditional test for the presence or absence of the $_POST['submit'] variable. If absent, a simple form with two input fields and a submit button is generated; if present, the script knows that user input has been submitted and so proceeds to inspect the contents of $_POST to ensure that all required input values are present and accounted for. Once this validation is completed without errors, a PDO connection is opened to the (MySQL) database server, and the values are saved to an appropriate database table.

…In With The New

Now, while the script works well, there’s no denying that it’s a little clunky. For one, there’s the switching from PHP into HTML, and then back again. For two, there’s the fact that the validation tests in the second half of the script need to be manually coded every time you add a new form field, even though the first two tests essentially do the same thing. And for three…

Ah, I don’t have a third reason. Maybe you do. Either way, the point is, there’s a better way to do it. Take a look:

<html>
<head></head>
<body>
<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm('shoppinglist');

// add input boxes
$form->addElement('text', 'name', 'Item name:', array('size' => 30));
$form->addElement('text', 'qty', 'Item quantity:', array('size' => 3));

// add submit button
$form->addElement('submit', null, 'Submit');

// set input validation rules
$form->addRule('name', 'ERROR: Missing value', 'required');
$form->addRule('qty', 'ERROR: Missing value', 'required');
$form->addRule('qty', 'ERROR: Incorrect data type', 'numeric');

// validate input 
if ($form->validate()) {
    // if valid, freeze the form
    $form->freeze();
    
    // retrieve submitted data as array
    $data = $form->exportValues();
    
    // process input
    // eg: save to database
    // attempt a connection
    try {
       $pdo = new PDO('mysql:dbname=test;host=localhost', 'user', 'pass');
    } catch (PDOException $e) {
       die('ERROR: Cannot connect: ' . $e->getMessage());
    }
    
    // create and execute INSERT query
    $name = $pdo->quote($data['name']);
    $qty = $pdo->quote($data['qty']);
    $sql = "INSERT INTO shoppinglist (name, qty) VALUES ($name, $qty)";
    $pdo->exec($sql) or die('ERROR: ' . implode(':', $pdo->errorInfo()));
    
    // close connection
    unset($pdo);
    
    // display success message and exit
    echo 'Thank you for your submission';    
    exit;
}

// render and display the form
$form->display();
?>
</body>
</html>

Pretty, isn’t it? Here’s what it looks like:

You’ll immediately notice three things about this script. First, with the exception of the HTML header and footer, there isn’t a single line of HTML code in the script; yet the output is indubitably an XHTML-compliant Web form. Second, although the validation to be performed on the form fields isn’t explicitly coded in, the form still appears to know which fields are required and what type of data they can contain. And third, it’s just plain pretty to look at!

Let’s go through this script line-by-line to better understand what’s going on:

1. The script begins by including the HTML_QuickForm class file, and then initializing a new instance of the HTML_QuickForm class.

<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm('shoppinglist');
?>

The class constructor can be passed an optional identification string; this string becomes the value of the form’s ‘name’ and ‘id’ attributes.

2. The HTML_QuickForm object’s addElement() method is then used to programmatically add various elements to the form.

<?php
// add input boxes
$form->addElement('text', 'name', 'Item name:', array('size' => 30));
$form->addElement('text', 'qty', 'Item quantity:', array('size' => 3));

// add submit button
$form->addElement('submit', null, 'Submit');
?>

The addElement() method accepts three (sometimes more) parameters: the type of element to add, the element’s name, and the corresponding human-readable element label. It’s also possible to set various other element attributes, by passing them to the addElement() method as an array of arguments.

3. The HTML_QuickForm object’s addRule() method now takes care of attaching validation “rules” to each element.

<?php
// set input validation rules
$form->addRule('name', 'ERROR: Missing value', 'required');
$form->addRule('qty', 'ERROR: Missing value', 'required');
$form->addRule('qty', 'ERROR: Incorrect data type', 'numeric');
?>

The addRule() method too accepts three (sometimes more) parameters: the name of the element to which the rule should be attached, the human-readable error message to display if the rule fails validation, and the type of validation required. HTML_QuickForm supports a bunch of built-in validation rules; you’ll see many of them as we progress through this tutorial, and you can even add your own custom rules.

The snippet above demonstrates two of these rules: the ‘required’ rule, which specifies that the named element cannot be empty; and the ‘numeric’ rule, which specifies that the value entered into the named element must be numeric.

4. The display() method renders the form and its elements. Once the form is submitted, the validate() method validates the input entered into the form on the basis of the rules set up with the addRule() method. Assuming the input passes validation, the exportValues() method is used to retrieve the submitted input as an associative array. Then, a PDO connection is opened to the database server and the input is saved to the database.

<?php
// validate input 
if ($form->validate()) {
    // if valid, freeze the form
    $form->freeze();
    
    // retrieve submitted data as array
    $data = $form->exportValues();
    
    // process input
    // eg: save to database

    // display success message and exit
    echo 'Thank you for your submission';    
    exit;
}

// render and display the form
$form->display();
?>

A brief word here about the last unexplained method in the script above, the freeze() method. By default, the form’s input elements are editable – the user can enter or modify values in the input boxes, check or uncheck radio buttons and checkboxes, and so on. However, the freeze() method, as the name suggests, freezes one or all of the form’s elements and renders them non-editable. In fact, if you try calling display() after freeze(), values entered into the form’s fields will be displayed as static text, rather than as editable values. “Freezing” a form is handy when displaying the results of a submission or simply to “lock” certain elements of the form for security or other purposes.

Going Elemental

With the broad overview out of the way, let’s delve a little deeper into the world of HTML_QuickForm. As you saw in the last listing, the package allows you to add elements to a form programmatically, via the addElement() method. There are 23 such elements, ranging from simple text input boxes to more complex date and hierarchical selection lists, and it’s useful to learn more about them. Consider the next example, which demonstrates some of the more common elements in action:

<html>
<head></head>
<body>
<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add input boxes
$form->addElement('text', 'fname', 'First name:', array('size' => 30));
$form->addElement('text', 'lname', 'Last name:', array('size' => 30));

// add password element
$form->addElement('password', 'pass', 'Password:', array('size' => 30));

// add radio buttons
$form->addElement('radio', 'sex', 'Sex:', 'Male', 'male');
$form->addElement('radio', 'sex', '', 'Female', 'female');

// add selection list
$country = $form->addElement('select', 'country', 'Country:', array(
  'us' => 'United States',
  'uk' => 'United Kingdom',
  'au' => 'Australia',
  'zz' => 'Other'
));

// add date
$date = $form->addElement('date', 'dob', 'Date of birth:', array('format' => 'dMY', 'minYear' => 1950, 'maxYear' => date('Y'))); 

// add multi-selection list
$source = $form->addElement('select', 'source', 'You heard about us from:', array(
  'web' => 'Our web site',
  'press' => 'Press reports',
  'ads' => 'Third-party advertisements',
  'other' => 'Other'
));
$source->setMultiple(true);

// add checkbox
$form->addElement('checkbox', 'contact', 'Please contact me with special offers');

// add hidden value
$form->addElement('hidden', 'ip', $_SERVER['REMOTE_ADDR']);

// add reset button
$form->addElement('reset', null, 'Reset Form');

// add submit button
$form->addElement('submit', null, 'Submit');

// print submitted values
// render and display the form
if ($form->isSubmitted()) {
  print_r($form->getSubmitValues());  
} else {
  $form->display();
}
?>
</body>
</html>

Here's a quick list of the elements used in the script above:

  • 'text' - a text input field
  • 'password' - a masked input field suitable for passwords;
  • 'hidden' - a field suitable for hidden values;
  • 'radio' - a radio button;
  • 'checkbox' - a checkbox;
  • 'select' - a multiple-choice list box;
  • 'date' - a series of list boxes for date selection;
  • 'submit' - a form submit button;
  • 'reset' - a form reset button;

Here's what the form looks like:

And here's a sample of the submitted data:

Array
(
    [ip] => 127.0.0.1
    [fname] => John
    [lname] => Doe
    [pass] => guessme
    [sex] => male
    [country] => au
    [dob] => Array
        (
            [d] => 9
            [M] => 9
            [Y] => 1956
        )

    [source] => Array
        (
            [0] => web
            [1] => ads
        )

    [contact] => 1
)

It's worth noticing how the parameters passed to addElement() change with the type of element being added. Contrast, for example, the code used to generate a text input field with that used to generate a selection list:

<?php
// text input
$form->addElement('text', 'fname', 'First name:', array('size' => 30));

// add selection list
$country = $form->addElement('select', 'country', 'Country:', array(
  'us' => 'United States',
  'uk' => 'United Kingdom',
  'au' => 'Australia',
  'zz' => 'Other'
));
?>

In the first case, the fourth parameter to addElement() holds an associative array of attributes and values appropriate to the HTML <input> element. In the second, the fourth parameter holds a list of

<?php
// add date
$date = $form->addElement('date', 'dob', 'Date of birth:', array('format' => 'dMY', 'minYear' => 1950, 'maxYear' => date('Y'))); 
?>

In this case, the array passed as the fourth parameter holds a list of custom configuration keys: the 'minyear' and 'maxYear' key specify the start and end years to display in the year selection box, while the exact configuration and formatting of the various date elements is specified in the 'format' key.

Different Strokes

It's interesting to note, also, that there's usually more than one way to skin the same cat. For example, you can generate a selection list by passing an array of options to addElement() as the fourth argument:

<?php
$colors = $form->addElement('select', 'color', 'Color:', array(
  'red'    => 'Scintillating Scarlet',
  'blue'   => 'Azure',
  'silver' => 'Metallic Chrome',
  'black'  => 'Midnight Marvel',
  'yellow' => 'Sunflower'
));
?>

But you can also generate the same list by calling the addOption() method, once each for each option:

<?php
$colors = $form->addElement('select', 'color', 'Color:');
$colors->addOption('Scintillating Scarlet', 'red');
$colors->addOption('Azure', 'blue');
$colors->addOption('Metallic Chrome', 'silver');
$colors->addOption('Midnight Marvel', 'black');
$colors->addOption('Sunflower', 'yellow');
?>

Once you've got your selection list generated, you can turn it into a multiple-selection list by calling the setMultiple() method. The second selection list in the previous example does this, so take a look at it for reference.

In a similar vein, there's also an alternative to the exportValues() method you saw in the script on the previous page: the getSubmitValues() method, which also returns an associative array of submitted values. What's the difference? The getSubmitValues() method returns all the data that was submitted, while the exportValues() method returns only that data for which elements actually exist in the form. This is somewhat safer, as it automatically filters out unauthorized data that may have been injected into the form submission by Joe Hacker.

What's the lesson here? It's a good idea to consult the API documentation for the package when adding new elements to your form, especially the more complex elements. A good starting point for this is the HTML_QuickForm manual, at http://pear.php.net/manual/en/package.html.html-quickform.intro-elements.php

The Default Option

Another useful method to learn about is the setDefaults() method, which allows you to set default values for some or all of the form's input fields. The argument to setDefaults() is an associative array, with keys corresponding to element names and values corresponding to element defaults. Here's an example:

<html>
<head></head>
<body>
<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add input boxes
$form->addElement('text', 'fname', 'First name:', array('size' => 30));
$form->addElement('text', 'lname', 'Last name:', array('size' => 30));

// add password element
$form->addElement('password', 'pass', 'Password:', array('size' => 30));

// add radio buttons
$form->addElement('radio', 'sex', 'Sex:', 'Male', 'male');
$form->addElement('radio', 'sex', '', 'Female', 'female');

// add selection list
$country = $form->addElement('select', 'country', 'Country:', array(
  'us' => 'United States',
  'uk' => 'United Kingdom',
  'au' => 'Australia',
  'zz' => 'Other'
));

// add date
$date = $form->addElement('date', 'dob', 'Date of birth:', array('format' => 'dMY', 'minYear' => 1950, 'maxYear' => date('Y'))); 

// add multi-selection list
$source = $form->addElement('select', 'source', 'You heard about us from:', array(
  'web' => 'Our web site',
  'press' => 'Press reports',
  'ads' => 'Third-party advertisements',
  'other' => 'Other'
));
$source->setMultiple(true);

// add checkbox
$form->addElement('checkbox', 'contact', 'Please contact me with special offers');

// add hidden value
$form->addElement('hidden', 'ip', $_SERVER['REMOTE_ADDR']);

// add reset button
$form->addElement('reset', null, 'Reset Form');

// add submit button
$form->addElement('submit', null, 'Submit');

// set default values
$form->setDefaults(array(
  'fname'   => 'Enter first name',
  'lname'   => 'Enter last name',
  'sex'     => 'female',
  'contact' => 1,
  'dob'     => array('d' => '2', 'M' => '4', 'Y' => '2007'),
  'country' => 'zz',
  'source'  => 'web,ads'
));

// print submitted values
// render and display the form
if ($form->isSubmitted()) {
  print_r($form->exportValues());  
} else {
  $form->display();
}
?>
</body>
</html>

And here's what it looks like:

Playing By The Rules

At this point, you're probably bored to death of hearing me mumble about form elements, and are wishing I'd move on to something else. So, without further ado, let's talk about input validation with HTML_QuickForm. There are two important methods here: the addRule() method, which lets you set up validation rules to check the input that your form receives from Web users, and the validate() method, which actually performs the validation and generates error messages as needed.

As you've already seen, the addRule() method accepts three (in some cases, four or five) parameters: the name of the element to which the rule should be attached, the human-readable error message to display if the rule fails validation, and the type of validation required. As of this writing, HTML_QuickForm supports the following thirteen built-in validation rules:

  • 'required' - the input value is mandatory;
  • 'maxlength' - the input value must not contain more than the specified number of characters;
  • 'minlength' - the input value must not contain less than the specified number of characters;
  • 'rangelength' - the length of the input value must fall within the specified range;
  • 'email' - the input value must correspond to the general form of an email address;
  • 'regex' - the input value must correspond to the specified regular expression;
  • 'lettersonly' - the input value can only contain alphabetic characters;
  • 'alphanumeric' - the input value can only contain alphanumeric characters;
  • 'numeric' - the input value can only contain numbers;
  • 'nopunctuation' - the input value cannot contain punctuation characters;
  • 'nonzero' - the input value cannot be zero;
  • 'callback' - the input value is to be passed to a named external function for validation;
  • 'compare' - the input value must be equal to another input element;

You've already seen the first of these, the 'required' rule, in action. Consider now the next example, which illustrates some of the others:

<html>
<head></head>
<body>
<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add input boxes
$form->addElement('password', 'pass', 'Password:', array('size' => 30));
$form->addElement('password', 'confirmpass', 'Confirm password:', array('size' => 30));

// add submit button
$form->addElement('submit', null, 'Submit');

// add validation rules
$form->addRule('pass', 'ERROR: Password missing', 'required');
$form->addRule('pass', 'ERROR: Password too short', 'minlength', 6);
$form->addRule('confirmpass', 'ERROR: Password missing', 'required');
$form->addRule(array('pass','confirmpass'), 'ERROR: Password mismatch', 'compare');

// render and display the form
if ($form->validate()) {
  $form->freeze();
  echo 'Thank you for your submission';
} else {
  $form->display();  
}
?>

</body>
</html>

This script sets up two text fields, one for a password and the other for password verification. Both are 'required', and the 'minlength' rule additionally ensures that the password input is at least six characters long. The 'compare' rule is used to verify that the input value entered into both fields is identical.

Easy peasy? Then how about something a little more complex?

<html>
<head></head>
<body>
<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add input boxes
$form->addElement('text', 'username', 'Username:', array('size' => 30));
$form->addElement('text', 'ssn', 'Social Security number:', array('size' => 15));
$form->addElement('text', 'tel', 'Telephone:', array('size' => 15));
$form->addElement('text', 'postcode', 'Postcode:', array('size' => 6));

// add submit button
$form->addElement('submit', null, 'Submit');

// add validation rules
$form->addRule('username', 'ERROR: Username must be between 4 and 8 characters', 'rangelength', array(4,8));
$form->addRule('username', 'ERROR: Username must be alphanumeric only', 'alphanumeric');
$form->addRule('ssn', 'ERROR: SSN format invalid', 'regex', '/^\d{3}\-\d{2}\-\d{4}$/');
$form->addRule('tel', 'ERROR: Telephone number format invalid', 'numeric');
$form->addRule('tel', 'ERROR: Telephone number must contain at least 6 characters', 'minlength', 6);
$form->addRule('postcode', 'ERROR: Postcode must contain only 6 characters', 'maxlength', 6);
$form->addRule('postcode', 'ERROR: Postcode must contain only 6 characters', 'minlength', 6);
$form->addRule('postcode', 'ERROR: Postcode must contain numbers only', 'nonzero');
$form->addRule('postcode', 'ERROR: Postcode must contain numbers only', 'numeric');

// render and display the form
if ($form->validate()) {
  $form->freeze();
  echo 'Thank you for your submission';
} else {
  $form->display();  
}
?>
</body>
</html>

This example introduces a few more of the built-in validation rules. First, the 'rangelength' rule ensures that the username supplied contains between four and eight characters, while the 'alphanumeric' rule further ensures that the username does not contain punctuation characters or special symbols. The 'regex' rule, applied to the Social Security Number, specifies the general form of a SSN and flags all those input values that do not conform to this format. Finally, the 'minlength' and 'maxlength' rules specify the number of characters that can be allowed in telephone numbers and postcodes, while the 'nonzero' and 'numeric' rules further constrain postcode input values to non-zero numbers only.

Notice that the arguments passed to addRule() in all these cases vary: for the 'minlength', 'maxlength' and 'regex' rules, an additional fourth parameter providing extra information for the constraint is specified, while no such information is supplied for the 'nonzero' and 'numeric' rules. Similarly, the argument passed to the fourth parameter of a 'rangelength' rule is not a single scalar value, but an array containing the range endpoints.

Things get even more complicated when you look at the 'compare' rule from the previous example - here, the first argument to addRule() is actually an array holding the names of the two fields to be compared. Again, the HTML_QuickForm manual, as well as the example script named 'rules-builtin.php' supplied with the package, are good places to learn more about these different flavours.

If you're sharp-eyed and play with the example above a little, you'll also notice something else: if a form element is not marked 'required', other validation rules specified for that element will remain dormant unless the user inputs a value in that field. This is remarkably good thinking on the part of HTML_QuickForm, and it's worth writing the developers a brief thank-you note for building in this simple, yet extremely important, bit of logic.

Rolling Your Own

It might seem hard to imagine right now, but HTML_QuickForm's built-in validation rules, although full-featured, don't cover every situation in the book. Often (but hopefully not too often), you'll come across situations where the built-in rules just aren't enough, and you need to roll your own validation routine. And that's when you'll reach for the 'callback' rule, which lets you pass input to a custom function for validation.

Here's an example, which asks the user to enter an email address and returns an error if the address already exists in the database - a common enough requirement of the average user registration form. Take a look at how HTML_QuickForm handles it:

<html>
<head></head>
<body>
<?php
// function to check if the input email address
// already exists in the database
function checkEmail($value) {
    try {
       $pdo = new PDO('mysql:dbname=test;host=localhost', 'user', 'pass');
    } catch (PDOException $e) {
       die('ERROR: Cannot connect: ' . $e->getMessage());
    }
    
    // create and execute SELECT query
    $value = $pdo->quote($value);
    $sql = "SELECT COUNT(*) FROM users WHERE email = $value";
    $ret = $pdo->query($sql) or die('ERROR: ' . implode(':', $pdo->errorInfo()));
    
    // if address exists, return false
    if ($ret->fetchColumn() > 0) {
      $flag = false;  
    } else {
      $flag = true;  
    }
    
    unset($pdo);
    return $flag;        
}

// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add input boxes
$form->addElement('text', 'email', 'Email address:', array('size' => 50));

// add submit button
$form->addElement('submit', null, 'Submit');

// add validation rules
$form->addRule('email', 'ERROR: Email address format invalid', 'email');
$form->addRule('email', 'ERROR: Email address already in use on the system', 'callback', 'checkEmail');

// render and display the form
if ($form->validate()) {
  $form->freeze();
  echo 'Thank you for your submission';
} else {
  $form->display();  
}
?>
</body>
</html>

Here, the checkEmail() callback function receives the user-supplied email address as input, internally queries the database for that email address and, if found, returns false to the caller. This is then translated into the corresponding user-level error message. This ability to create any form of user-defined callback function to validate input lets you go beyond HTML_QuickForm's built-in capabilities and handle almost any kind of situatin requiring validation.

Here's another example, this one using a callback function that internally invokes PHP's checkdate() function to verify if a date entered by the user is a "real" date:

<html>
<head></head>
<body>
<?php
// function to check date validity
function checkDateInput($value) {
  if (checkdate($value['M'], $value['d'], $value['Y'])) {
    return true;
  } else {
    return false;
  }      
}
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add date
$date = $form->addElement('date', 'issue', 'Passport issue date:', array('format' => 'dMY', 'minYear' => 1950, 'maxYear' => date('Y'))); 
$date = $form->addElement('date', 'expiry', 'Passport expiry date:', array('format' => 'dMY', 'minYear' => 1950, 'maxYear' => date('Y')+25)); 

// add date validation
$form->addRule('issue', 'ERROR: Date is invalid', 'callback', 'checkDateInput');
$form->addRule('expiry', 'ERROR: Date is invalid', 'callback', 'checkDateInput');

// add submit button
$form->addElement('submit', null, 'Submit');

// render and display the form
if ($form->validate()) {
  $form->freeze();
  echo 'Thank you for your submission';
} else {
  $form->display();  
}
?>
</body>
</html>

A Little Magic

One final point worth noting: by default, HTML_QuickForm performs all its validation on the server side. Server-side validation involves checking the values submitted to the server on the server itself. However, you can instruct HTML_QuickForm to also perform client-side input validation, using a client-side scripting language like JavaScript. Have you ever fallen off a log? I haven't, but doing this is as simple as that apparently is - all you need to do is add the keyword 'client' to your addRule() calls as an additional argument. Take a look:

<html>
<head></head>
<body>
<?php
// initialize HTML_QuickForm object
require_once 'HTML/QuickForm.php';
$form = new HTML_QuickForm();

// add input boxes
$form->addElement('password', 'pass', 'Password:', array('size' => 30));
$form->addElement('password', 'confirmpass', 'Confirm password:', array('size' => 30));

// add submit button
$form->addElement('submit', null, 'Submit');

// add validation rules
$form->addRule('pass', 'ERROR: Password missing', 'required', null, 'client');
$form->addRule('pass', 'ERROR: Password too short', 'minlength', 6, 'client');
$form->addRule('confirmpass', 'ERROR: Password missing', 'required', null, 'client');
$form->addRule(array('pass','confirmpass'), 'ERROR: Password mismatch', 'compare', null, 'client');

// render and display the form
if ($form->validate()) {
  $form->freeze();
  echo 'Thank you for your submission';
} else {
  $form->display();  
}
?>
</body>
</html>

Look inside the source code of the page generated by this script, and you'll see a bunch of JavaScript statements automatically added to the form. That's HTML_QuickForm's client-side validation at work...and you didn't have to lift a finger! Does this thing rock or what?

And that's about it for this tutorial. Over the last few pages, I took you on a whirlwind tour of the HTML_QuickForm package, showing you how to use it to programmatically generate form elements and validation code. I also showed you how to set default values, apply your own custom validation routines to user input, and supplement the default server-side validation with automatically-generated client-side validation.

All of this is, however, just the tip of the iceberg. HTML_QuickForm lets you do much, much more with your forms, including applying templates to control their appearance, create multi-page forms, write and register your own custom validation rules, and use more sophisticated form elements for greater power and usability. I'll be delving into these and other topics in the second part of this tutorial, so make sure you come back for that. Until then...happy coding!

Copyright Melonfire 2007. All rights reserved.

6 Responses to “Generating and Validating Web Forms With PEAR HTML_QuickForm”

  1. _____anonymous_____ Says:

    Un buen articulo para seguir. Paso a paso y no hay pierde todo funciona!!!

  2. _____anonymous_____ Says:

    Excellent article — thank you! I can’t wait for the next edition.

  3. _____anonymous_____ Says:

    How do you validate inputs that belong to an array?

    Instance,

    $form->addRule(‘airports[]‘,’Code required’,'required’);

    And

    $form->addRule(‘airports[0]‘,’Code required’,'required’);

    Doesn’t work when there is a posted array of $airports.

  4. ivanpiffer Says:

    Thankh you for the article, very easy to follow!!!

  5. kambingx Says:

    In development how about the client validation?

  6. kungfoofool Says:

    I wrote an article on how to use the QuickForm_Renderer to revamp the HTML from old-and-busted tabular layouts to the new-and-improved div-form layouts. Your local CSS guru will thank you.

    The article can be read here: <a href="http://realm3.com/articles/how_to_format_forms_with_pear_html_quickform_renderer">How To Format Forms With PEAR Html Quickform Renderer</a>

    I could see it being pretty useful for those getting familar with PEAR’s QuickForm.