Introduction

Programmers are often told not to reinvent the wheel, and that the best programmers borrow from others before they start to build something themselves. PHP, being primarily a web language, sees a lot of reinventing in regards to form display, processing and validation. However, there's a great PEAR package that needs a little more exposure: HTML_QuickForm. It makes handling the rendering and display of forms, and more usefully, both client and server-side validation, quick and easy. This article will take you through the basics of that package. It assumes familiarity with HTML form elements, and reasonably basic PHP skills.

Installing HTML_QuickForm

The package has only two requirements: a version of PHP that is at least 4.2, and the HTML_Common package. At the time of writing, HTML_QuickForm 3.2.7 is the newest version of the package, and it requires HTML_Common 1.2.1. There are moves underfoot to rewrite both of these packages for PHP 5 (in the form of HTML_QuickForm2, and HTML_Common2), but as yet neither has been released.
You can check what PEAR packages are already installed by using pear list:
pear list
Installed packages:
===================
Package        Version State
Archive_Tar    1.1     stable
Console_Getopt 1.2     stable
DB             1.6.2   stable
Date           1.4.6   stable
HTTP           1.2.2   stable
Image_Canvas   0.3.0   alpha
Image_Color    1.0.2   stable
Image_Graph    0.7.2   alpha
Mail           1.1.3   stable
Net_SMTP       1.2.6   stable
Net_Socket     1.0.1   stable
PEAR           1.3.2   stable
Validate       0.6.3   beta
XML_Parser     1.0.1   stable
XML_RPC        1.1.0   stable
This machine doesn't have either HTML_QuickForm or HTML_Common, so they'll need to be installed.
pear install HTML_Common
downloading HTML_Common-1.2.3.tgz ...
Starting to download HTML_Common-1.2.3.tgz (4,746 bytes)
.....done: 4,746 bytes
install ok: HTML_Common 1.2.3

pear install HTML_QuickForm
downloading HTML_QuickForm-3.2.7.tgz ...
Starting to download HTML_QuickForm-3.2.7.tgz (102,475 bytes)
........................done: 102,475 bytes
install ok: HTML_QuickForm 3.2.7

Displaying the form

The code required to display a form is simple. Let's start with an example:
 <?php
  require_once "HTML/QuickForm.php"; // tell PHP to include the QuickForm package

  $form = new HTML_QuickForm('register', 'post');  // instantiate the object
  $form->addElement('text', 'firstName', 'Enter first name'); // add a text element
  $form->addElement('password','password', 'Enter your password'); // add a password element
  $form->addElement('textarea','ta','Description'); // add a textarea element
  $form->addElement('submit','sb','Submit form'); // add a submit button element

  $form->display();
?>
It should be fairly self-explanatory what this snippet does. Include the package, instantiate the object, then add a text form element (called firstName, with the text Enter your first name appearing next to it, and then add a password form element called password, with the text Enter your password next to it. Here's what the resulting HTML looks like:
<form action="/phpbuilder/html_quickform11.php" method="post" name="register" id="register">
<div>
<table border="0">

	<tr>
		<td align="right" valign="top"><b>Enter first name</b></td>
		<td valign="top" align="left">	<input name="firstName" type="text" value="" /></td>
	</tr>
	<tr>

		<td align="right" valign="top"><b>Enter your password</b></td>
		<td valign="top" align="left">	<input name="password" type="password" value="" /></td>
	</tr>
	<tr>
		<td align="right" valign="top"><b>Description</b></td>
		<td valign="top" align="left">	<textarea name="ta"></textarea></td>

	</tr>
	<tr>
		<td align="right" valign="top"><b></b></td>
		<td valign="top" align="left">	<input name="sb" value="Submit form" type="submit" /></td>

	</tr>
</table>
</div>
</form>
As you can see HTML_QuickForm allows you to get away with a lot less typing to get the same output, so even without all the other benefits, the abstraction will at least save you some time (although I wouldn't recommend it for just this purpose!). It also produces (with a minor exception) markup that's XHTML Strict-compliant (with the minor exception of the legacy form name attribute, which can be easily removed). The above example utilised a text, a textarea and a password element. Here's a list of the other elements HTML_QuickForm can add, along with the HTML_QuickForm name, and the HTML equivalent. They're all given straighforward names, so if you know the HTML term, you'll know the term to use here.
elementHTML
button<input type="button" />
checkbox<input type="checkbox" />
file<input type="file" />
hidden<input type="hidden" />
image<input type="image" />
password<input type="password" />
radio<input type="radio" />
reset<input type="reset" />
select<select>. The <option> elements can be loaded from either an array or a database.
submit<input type="submit" />
text<input type="text" />
textarea<textarea>
xbutton<button>
There are also various custom element types. We're not going to look at any examples in this tutorial, but they're listed here for reference.
advcheckboxAn advanced checkbox type, allowing checkboxes to pass multiple values.
autocompleteA text field with autocomplete. It's a normal text element, but at each keypress JavaScript is used to read an array and autocomplete if there's anything matching.
dateA group of elements for inputting dates and times
groupAllows several elements to be grouped into a single new entity.
headerAllows a heading to be added to the form.
hiddenselectA select element containing hidden elements for everything already selected with the methods setDefaults() or setConstants().
hierselectA select element where choosing one item from the first select will dynamically populate a linked second select element.
htmlUsed to be used for adding raw HTML to a form, it's now deprecated.
linkA link type field
staticStatic data

Validating the data

As you've probably discovered, clicking on the submit button in our earlier example doesn't yet do much. If you look at the form action attribute, you'll notice that the same script is called. To make things easier for itself, HTML_QuickForm forms reference themselves, so the same script is responsible for generating the form, validating it, and processing it if successful. To make something happen, we'll need to add logic to process the submitted form, as well as validate the data.
Validation is the bane of many developers, but HTML_QuickForm makes it really easy to validate both on the client-side and the server-side. It does so with the addRule() method. Then, to actually perform the validation, the validate() method is called. We'll now only display the form if it hasn't yet been validated (the code to process the form would be called at that point). All very simple! Here's an example with two rules, one stating that the first name is a required field, and the other that the first name must only contain letters (and this excludes names with a dash, so you may not want to do this in a real application!):
<?
  require_once "HTML/QuickForm.php";

  $form = new HTML_QuickForm('register', 'post');
  $form->addElement('text', 'firstName', 'Enter first name');
  $form->addElement('password','password', 'Enter your password');
  $form->addElement('textarea','ta','Description');
  $form->addElement('submit','sb','Submit form');

  $form->addRule('firstName', 'Your name is required', 'required');
  $form->addRule('firstName', 'Your name can only contain letters','lettersonly');

  if ($form->validate()) {
    // processing code goes here.
    echo 'Success!';
  }
  else {
    $form->display();
  }
?>
Try and see what happens when you submit this form with invalid data, and then again with valid data. A red star indicates that the first name is a required field. If the data is entered incorrectly (either left out, or containing anything but letters) an appropriate error message is displayed, also in red.
The above example uses the required and lettersonly rules. Here's a list of all the rules that can be applied:
rule argument description
alphanumeric   Can only contain alphanumeric characters (letters and numbers).
compare   Compares two values.
email true (which applies a DNS check) Must be a valid email (in syntax) (checks for a valid hostname if the argument is set to true).
filename $regex The filename of the uploaded file must match the regular expression in $regex.
lettersonly   Can only contain letters.
maxfilesize $maxsize The filename of the uploaded file cannot be larger than $maxsize bytes.
maxlength $maxlength Can be at most $maxlength characters in length.
mimetype $mimetype MIME type of the uploaded file must either be of type $mimetype (if $mimetype is scalar), or match one of the elements in $mimetype (if it's an array).
minlength $minlength Must be at least $minlength in length.
numeric   Must contain a valid integer or decimal number.
nonzero   Cannot be zero.
nopunctuation   Cannot contain any punctuation characters, which include: ( ) . / * ^ ? # ! @ $ % + = , " ' > < ~ [ ] { }.
rangelength $minlength,$maxlength Must be inclusively between $minlength and $maxlength characters in length.
regex $regex Must match the regular expression $regex.
required   Cannot be blank
uploadedfile   Must contain a successfully uploaded file.
Let's look at some more examples and features. One extremely useful feature is that you can also validate on the client-side as well, using the 'client' flag. It's good practice to validate on the client-side, as this makes the user's life that much easier (they don't need to wait for a response from the server), but you should always validate on the server-side as well, as not all clients have JavaScript enabled. We also look at the comparison rule (in this example two fields cannot be the same), and a custom rule, which allows you to write a function to use any criteria you wish.
<?
  require_once "HTML/QuickForm.php";

  $form = new HTML_QuickForm('register', 'post');
  $form->addElement('text', 'firstName', 'Enter first name');
  $form->addElement('password','password', 'Enter your password');
  $form->addElement('text','customer_email','Enter an email');
  $form->addElement('textarea','ta','Description');
  $form->addElement('submit','sb','Submit form');

  $form->addRule('firstName', 'Your name is required', 'required');
  $form->addRule('firstName', 'Your name can only contain letters','lettersonly');
  $form->addRule('firstName', 'You must enter a first name', 'required', null, 'client');
  $form->addRule('firstName', 'Your name cannot be less than two characters!','minlength',2,'client');
  $form->addRule('customer_email', 'Please enter a valid email','email',true,'client');
  $form->AddRule(array('password','firstName'),'Your password and first name cannot be the same!','compare','!=');
  
  $form->registerRule('no_symbol','function','no_symbol_f');
  $form->addRule('firstName','Sorry, your name cannot be Symbol','no_symbol');

  if ($form->validate()) {
    // processing code goes here.
    echo 'Success!';
  }
  else {
    $form->display();
  }
  
  function no_symbol_f($element_name,$element_value) {
    if ($element_value == 'Symbol') {
      return false;
    }
    else {
      return true;
    }
  }

?>
And here's what's generated:
 <script type="text/javascript">
//<![CDATA[
function validate_register(frm) {
  var value = '';
  var errFlag = new Array();
  var _qfGroups = {};
  _qfMsg = '';

  value = frm.elements['firstName'].value;
  if (value == '' && !errFlag['firstName']) {
    errFlag['firstName'] = true;
    _qfMsg = _qfMsg + '\n - You must enter a first name';
  }

  value = frm.elements['firstName'].value;
  if (value != '' && value.length < 2 && !errFlag['firstName']) {
    errFlag['firstName'] = true;
    _qfMsg = _qfMsg + '\n - Your name cannot be less than two characters!';
  }

  value = frm.elements['customer_email'].value;
  var regex = /^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/;
  if (value != '' && !regex.test(value) && !errFlag['customer_email']) {
    errFlag['customer_email'] = true;
    _qfMsg = _qfMsg + '\n - Please enter a valid email';
  }

  if (_qfMsg != '') {
    _qfMsg = 'Invalid information entered.' + _qfMsg;
    _qfMsg = _qfMsg + '\nPlease correct these fields.';
    alert(_qfMsg);
    return false;
  }
  return true;
}
//]]>
</script>

<form action="/phpbuilder/html_quickform33.php" method="post" name="register" id="register" onsubmit="try { var myValidator = validate_register; } catch(e) { return true; } return myValidator(this);">
<div>
<table border="0">

	<tr>
		<td align="right" valign="top"><span style="color: #ff0000">*</span><b>Enter first name</b></td>
		<td valign="top" align="left">	<input name="firstName" type="text" /></td>

	</tr>
	<tr>
		<td align="right" valign="top"><b>Enter your password</b></td>
		<td valign="top" align="left">	<input name="password" type="password" /></td>
	</tr>
	<tr>
		<td align="right" valign="top"><b>Enter an email</b></td>

		<td valign="top" align="left">	<input name="customer_email" type="text" /></td>
	</tr>
	<tr>
		<td align="right" valign="top"><b>Description</b></td>
		<td valign="top" align="left">	<textarea name="ta"></textarea></td>
	</tr>
	<tr>

		<td align="right" valign="top"><b></b></td>
		<td valign="top" align="left">	<input name="sb" value="Submit form" type="submit" /></td>
	</tr>
	<tr>
		<td></td>
	<td align="left" valign="top"><span style="font-size:80%; color:#ff0000;">*</span><span style="font-size:80%;"> denotes required field</span></td>
	</tr>

</table>
</div>
</form>
The compare rule allows other operators besides the not equal to (!=) used in this example, such as < or >. If you leave out the operator altogether, it's assumed to be =, meaning that the two elements must be the same (typically used to check whether a user has entered their password or email address correctly).

Processing the form data

Up till now, we've simply displayed Success! if the form has been validated. That's not particularly useful. Of course, we could put as much code as we want there, but that's not particularly elegant. HTML_QuickForm provides a process() method, which calls a function and passes the submitted values. Here's the method in action:
<?
  require_once "HTML/QuickForm.php";

  $defaults_= array("firstName"=>"Ian",
                 "password"=>"abc",
                 "ta"=>"Describe yourself here");

  $form = new HTML_QuickForm('register', 'post');
  $form->addElement('text', 'firstName', 'Enter first name');
  $form->addElement('password','password', 'Enter your password');
  $form->addElement('textarea','ta','Description');
  $form->addElement('submit','sb','Submit form');

  $form->addRule('firstName','Your name is required', 'required');

  if ($form->validate()) {
    // processing code goes here.
    $form->process('process_data', false);
  }
  else {
    $form->setDefaults($defaults);
    $form->display();
  }

  // For now just display the submitted vales, but you'll want to do a lot more
  function process_data ($values) {
    foreach ($values as $key=>$value) {
      echo $key."=".$value."<br>";
    }
  }
?>

Formatting the form

Early versions of HTML_QuickForm were not that flexible when it came to changing the layout. However, since version 3.0, behaviour became based on the the well-known Visitor design pattern. There are eight renderers available, and the following template engines are supported directly: Smarty, HTML_Template_Sigma, HTML_Template_IT, HTML_Template_Flexy. Looking at these various renderers and template engines in detail is beyond the scope of this article, but the following example shows briefly that it is possible to substantially customise your form output. My example is fantastically ugly just so you don't miss the effects! Rest assured that if you know a particular template engine, you'll find it quite easy to make use of.
<?
  require_once "HTML/QuickForm.php";

  $defaults_= array("firstName"=>"Ian",
                 "password"=>"abc",
                 "ta"=>"Describe yourself here");

  $form = new HTML_QuickForm('register', 'post');
  $form->addElement('text', 'firstName', 'Enter first name');
  $form->addElement('password','password', 'Enter your password');
  $form->addElement('textarea','ta','Description');
  $form->addElement('submit','sb','Submit form');

  $form->addRule('firstName','Your name is required', 'required');

  $renderer =& $form->defaultRenderer();

  $special_element_template='
<tr>
  <td align="right" valign="top">
    <!-- BEGIN required --><span style="color: magenta">*</span><!-- END required -->
    <font size="+22">{label}</font>
  </td>
    <td valign="top" align="left">
    <!-- BEGIN error --><span style="color: magenta">{error}</span><br /><!-- END error -->
  {element}</td>
</tr>';

  $renderer->setElementTemplate($special_element_template);

  if ($form->validate()) {
    // processing code goes here.
    echo 'Success!';
  }
  else {
    $form->setDefaults($defaults);
    $form->display();
?>

Conclusion

I'm sure you've found it quite easy to use, but there's a lot more to this package. The official HTML_QuickForm page on the PEAR website has an API and, unusual for most PEAR packages, end-user documentation as well. And don't forget to look out for the PHP5 version of the package, HTML_QuickForm2, when it's released. Good luck!