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


Quick and Clean PHP Forms

by Adam Smith
09/13/2007

Introduction

As its name suggests, the PHP Extension and Application Repository (PEAR) library called HTML_QuickForm can be used to quickly and cleanly to produce validating HTML forms, relieving the developer of the tedium that often accompanies such tasks. HTML_QuickForm provides developers with a declarative approach to defining form fields and their associated validation rules and automatically generates the necessary HTML and JavaScript code needed to display the form accordingly. HTML_QuickForm also automatically validates submitted forms as indicated and allows developers to define a function that further handles valid submissions.

This tutorial presents a basic implementation of HTML_QuickForm to produce a common email contact form and explores ways to get the most from this powerful library.

When Is It Appropriate to Use HTML_QuickForm?

You may have inherited a legacy web site that is either entirely or mostly static and needs to have a dynamic form or two added to it. Or you may want to build a more dynamic site with a handful of forms that need to be processed flexibly. Maybe you are just mocking up some screens for a proposed web application and want a quick way to make those screens at least somewhat operational. The wide availability of PHP and its relative ease of use make it an ideal environment for adding dynamic functionality to sites that may be largely static otherwise, and libraries like HTML_QuickForm can make adding that functionality even easier.

Or maybe you are building a web application in a more comprehensive PHP framework but you need to add a form that doesn't fit well with the flow and design of the rest of the application. Or maybe because of your development philosophy, personal disposition, or level of experience, you are skeptical of highly structured web frameworks. You want to program your application from scratch your way, but you are open to taking advantage of third party libraries for solving specific challenges. In any of these cases, the HTML_QuickForm library can be used to create robust, validating forms that easily allow you to plug in your own code for processing valid form submissions.

An email contact form is a classic example of a form that gets tacked onto both static sites and complete web applications alike. This tutorial will describe how to use the PEAR HTML_QuickForm library to create a self-contained, pure PHP script that displays, validates, and processes a contact form, ultimately formatting the submitted data as an email message and sending it.

Finally, consider when it may be appropriate not to use a library like HTML_QuickForm. Complex web applications that are heavily form-driven, particularly if those forms perform primarily CRUD operations on a database, may benefit much more from a more complete, integrated web framework. CakePHP, is one such developer-friendly PHP framework inspired by Ruby on Rails.

Implementing a Contact Form with HTML_QuickForm

Previously through ONLamp, Keith Edmunds wrote a very nice tutorial called "Getting Started with PHP's HTML_QuickForm" that will be used here as a starting point. Following his example code closely, let's begin with this basic implementation of a contact form:

Example 1.

  
require_once "HTML/QuickForm.php";

$form = new HTML_QuickForm('contact_form', 'post');

$form->addElement('text',     'name',          'Name');
$form->addElement('textarea', 'address',       'Mailing Address');
$form->addElement('text',     'email_address', 'Email Address');
$form->addElement('text',     'home_phone',    'Home Phone');
$form->addElement('text',     'cell_phone',    'Cell Phone');
$form->addElement('text',     'subject',       'Subject');
$form->addElement('textarea', 'message',       'Message');   
$form->addElement('submit',   'submit',        'Submit');

$form->applyFilter('__ALL__', 'trim');

$form->addRule('name', 
               'Please enter your name.', 
               'required');
$form->addRule('email_address', 
               'Please enter your email address.', 
               'required');
$form->addRule('home_phone', 
               'Please enter your home phone number.', 
               'required');
                
if ($form->validate()) {
  $form->freeze();
  $form->process("process_data", false);
}
else {
  $form->display();
}

function process_data ($values) {
  require_once "emailHelper.php";
  emailValues($values);
}

The emailValues function will be described more fully later, but for now, it suffices to say that this is the helper function that formats and emails the validated form input.

As is, this code is simple, its intent is clear, and it demonstrates that like any good library, HTML_QuickForm provides a convenient way of performing a well defined task, in this case authoring and validating a typical contact form. HTML_QuickForm is so useful, you may find yourself using it often for similar forms, and as you integrate libraries like this into your personal set of tools, you may want to start to use it at a higher level, in a more idiosyncratic way that fits your needs and your personal programming style. To that end, let's begin exploring a few ways that this example could be further simplified, by introducing intelligent conventions and removing duplicated code.

First, it may be that often, and perhaps always, field names can be written in a conventional way such that they could then be used to programmatically generate a human-friendly form label when displaying the form. This approach could also be used to format the field name for the email message. In fact, the sample code already follows a typical approach of naming fields in complete words, separated by underscores, so a function can be written that replaces the underscores with spaces and converts the resulting string to title case, yielding something appropriate to use as a field label:

  
function createFieldLabel($field_name) {
  return ucwords(ereg_replace('[_]', ' ', $field_name));
}

For example, home_phone becomes "Home Phone." Many frameworks use a similar convention.

This function can now be used to simplify the creation of the form elements themselves. A simple multi-dimensional array mapping field names to HTML element types could be iterated over to call the function that generates the form elements:

  
$fields = array(
                array('name'          => 'text'    ),
                array('address'       => 'text'    ),
                array('email_address' => 'text'    ),
                array('home_phone'    => 'text'    ),
                array('cell_phone'    => 'text'    ),
                array('subject'       => 'text'    ),
                array('message'       => 'textarea'),
                array('submit'        => 'submit'  ),
               );
foreach ($fields as $key=>$value) {
  $form->addElement($value, $key, createFieldLabel($key));
}

Specifying form fields is simpler and the duplicate calls to addElement have been removed. This is a small optimization, but it's advisable to start as simply as possible. The true value of this approach can be judged more accurately as we expand its scope.

Adding validation rules points to a small amount of duplication when specifying field names. If a field name passed to addElement changes, its corresponding addRule constraint must also change. But, if a form element's validation rules can be added to the form at the same time as the element itself, the duplication and the potential for introducing that particular type of bug can be removed.

It is only at this point, when trying to introduce the field validations to the existing multi-dimensional array, that shortcomings of this approach become clear:

 
$fields = array('name'=>array('field_type'        =>'text',
                                 'validation_method' =>'required',
                                 'validation_message'=>'Please enter your name.',
                                ),
               ...
               );

The array syntax is becoming unwieldy, resulting in code that is more complicated, not less. The need to specify known array keys like field_type is also quite fragile. A better way is to start over, implement a real refactoring, and wrap both addElement and addRule calls in methods with explicit parameters:

function addRule($form, 
                 $field_name, 
                 $validation_method) {
  // this global variable seemed appropriate for now 
  global $default_messages; 
  if ($validation_method != '') {
    $validation_message = sprintf($default_messages[$validation_method], ereg_replace('[_]', ' ', $field_name));
    $form->addRule($field_name, 
                      $validation_message, 
                      $validation_method);
  }
}

function addElement($form, 
                    $field_name, 
                    $field_type, 
                    $validation_method='') {
  $form->addElement($field_type, 
                       $field_name, 
                       createFieldLabel($field_name));
  addRule($form, 
          $field_name, 
          $validation_method);
}

This new addElement function is not much more complicated to use than its original namesake that it is now wrapping, and it simultaneously calls a similar addRule wrapper function that simplifies adding validation rules. The parameters have also been ordered in a way that is perhaps more intuitive than the order dictated by the HTML_QuickForm API.

Additionally, default validation messages have been defined for the validation methods used, so that informative messages that also take advantage of the form field naming convention will be used automatically. The reasoning for this approach is that, apart from specifying the name of the field that failed validation, the message itself is more closely associated with a particular rule than any field to which that rule is applied. The following default messages illustrate this:

$default_messages = array ('required'=>'Please enter your %s.',
                           'email'   =>'Please enter a valid %s.'
                          );

Each message is associated with a particular rule in a format that can be processed by the PHP sprintf function, adding the appropriate field name in a human-friendly format based on the form field naming convention function defined above. These default messages avoid some duplication in specifying validation messages and ensure that those messages are rendered consistently.

The Complete Implementation

At this point, it should be clear that these new wrapper functions could be logically implemented within a subclass of HTML_QuickForm, that would in turn make the contact form code cleaner still. The subclass, called HTML_QuickerForm, is implemented in a file of the same name:

  require_once('HTML/QuickForm.php');
  
  class HTML_QuickerForm extends HTML_QuickForm {
  
    function HTML_QuickerForm($formName='', 
                              $method='post', 
                              $action='', 
                              $target='', 
                              $attributes=null, 
                              $trackSubmit = false) {
       parent::HTML_QuickForm($formName, 
                              $method, 
                              $action, 
                              $target, 
                              $attributes, 
                              $trackSubmit);
       $this->applyFilter('__ALL__', 'trim');
     }
        
    function addDefaultMessages($default_messages) {
      $this->default_messages = $default_messages;
    }
    
    // replaces underscores with spaces 
    // and capitalizes the resulting words
    function createFieldLabel($field_name) {
      return ucwords(ereg_replace('[_]', ' ', $field_name));
    }
    
    function addQuickRules($field_name, 
                           $validation_methods) {
      if ($validation_methods != '') {
        $methods = split(',', $validation_methods);
        foreach ($methods as $method) {
          $validation_message = sprintf($this->default_messages[$method], ereg_replace('[_]', ' ', $field_name));
          $this->addRule($field_name, 
                         $validation_message, 
                         $method);
        }
      }                  
    }
    
    function addQuickElement($field_name, 
                             $field_type, 
                             $validation_methods='', 
                             $attributes='') {
      $this->addElement($field_type, 
                        $field_name, 
                        $this->createFieldLabel($field_name), 
                        $attributes);
      $this->addQuickRules($field_name, 
                           $validation_methods);
    }
    
    function executeForm() {    
      if ($this->validate()) {
        $this->process("process_data", false);
      } else {
        $this->display();
      }
    }
  
  }

First, notice that the constructor has been overridden to automatically apply the trim filter to all form fields by default, a best practice learned from Keith Edmunds' tutorial. The $default_messages variable is now an instance variable instead of a global, and I renamed the functions so that, as methods of this subclass, they will not override the similarly named existing implementations in the super class they were meant to wrap. I also added the ability to add attributes to form elements to round out the functionality.

Further subclasses could be defined for common implementations of the process method callback. Since emailing the validated form data is a typical requirement of contact forms, this can be implemented in a subclass of HTML_QuickerForm with a small amount of PHP reflection trickery:

class HTML_QuickMailForm extends HTML_QuickerForm {
    function executeForm() {    
    if ($this->validate()) {
      $this->process(array(get_class($this), 'process_data'), false);
    } else {
      $this->display();
    }
  }
  
  function process_data($values) {
      require_once "emailHelper.php";
      emailValues($values);
  }
}

The email contact form can now be rewritten in a way that succinctly specifies only what is necessary for this particular form implementation:

Example 2.

require_once('HTML_QuickerForm.php');
$form = new HTML_QuickMailForm('contact_form', 'post');

$default_messages = array ('required' => 'Please enter your %s.',
                            'email'    => 'Please enter a valid %s.'
                          );
$form->addDefaultMessages($default_messages);

$form->addQuickElement('name',          'text',     'required',      'class=name'); 
$form->addQuickElement('address',       'text'      ); 
$form->addQuickElement('email_address', 'text',     'required,email'); 
$form->addQuickElement('home_phone',    'text',     'required'      );
$form->addQuickElement('cell_phone',    'text'      ); 
$form->addQuickElement('subject',       'text'      ); 

$textareaAttributes = array("wrap" => "virtual", 
                            "cols" => "60"
                            );
                            
$form->addQuickElement('message',       'textarea', '', $textareaAttributes); 
$form->addQuickElement('submit',        'submit'    ); 
$form->executeForm();

This version of the contact form actually contains more functionality, because more validations have been added, as have element attributes, for the sake of giving a more complete example of how to use the new subclass. For a given form element, multiple validation methods can be specified using comma separated strings, as shown by the email_address field, and multiple attributes can be specified using an array of appropriate values, as shown by the message field. In each case, single strings can also be used when only one value is needed, as shown by the name field which specifies only one validation method and one attribute.

Thanks to the power of HTML_QuickForm, and a little time spent personalizing how it may be used typically, this is all the coding needed to create an email contact form in the future.

Deploying the Contact Form

Up to this point, the actual email helper function used by the contact form has remained a mystery. Here is a possible implementation, saved in the aforementioned emailHelper.php file:

function emailValues (&$values) {
    $body = '';
    foreach ($values as $key=>$value) {
      if ($key != 'submit') {
        // replaces underscores with spaces 
        // and capitalizes the resulting words
        $body .= ucwords(ereg_replace('[_]', ' ', $key));
        $body .= "\r\n";
        if ($value == '') {
          $value = '(nothing entered)';
        }
        $body .= $value;
        $body .= "\r\n\r\n";
      }
    }
    
    $to="receiver@domain.com";
    $from="sender@domain.com";
    $subject = $values['subject'];
    $confirmation = "Thank you, your message has been sent.";

    require_once 'Mail.php';
    $smtp = Mail::factory('smtp');
    $headers = array ('From' => $from,
                      'To' => $to,
                      'Subject' => $subject);
    $mail = $smtp->send($to, $headers, $body);

    if (PEAR::isError($mail)) {
      $confirmation = $mail->getMessage() ;
    } 

    echo "<p>" . $confirmation . "</p>";
}

This function also takes advantage of the form field naming convention defined earlier to help format the email in a human-friendly way. Obviously, at the very least, you should consider extracting to a configuration file some of the strings hard-coded in this function.

Another PEAR library, Mail, which in turn requires the PEAR Net_SMTP library, is used instead of the built-in PHP mail function, as this approach is more flexible and easier to use in some circumstances. In particular, many people encounter issues with sendmail, the underling program called by the PHP mail function, and as most servers already have SMTP installed and available, using the SMTP method may be easier. SMTP may or may not be installed on your development machine, however, so consult your distribution's documentation if you encounter problems. On Ubuntu, for example, you can install SMTP using the following command:

sudo apt-get install postfix

HTML_QuickForm itself requires PHP4, the HTML_Common PEAR library and the PEAR base system. If you go to the HTML_QuickForm home page on the PEAR site, you will see that it has been superseded by HTML_QuickForm2, and its developers urge the use of that version. But if like many people, you are deploying onto a PHP4 installation, note that HTML_QuickForm2 requires PHP5, and as the original HTML_QuickForm is still being maintained with bug fixes and security updates, this is likely to be the version you will want to use. HTML_QuickForm2 was originally just a rewrite of HTML_QuickForm to make it work with the PHP5 E_STRICT setting, so the HTML_QuickForm documentation and examples should also be applicable to HTML_QuickForm2 with minimal changes. New functionality is also being added to HTML_QuickForm2, but these features have not been openly documented yet at the time this tutorial was written, so expect to read code to take advantage of any new features.

Finally, consider that often you may not have detailed knowledge of the environment onto which your code will ultimately be deployed or how that environment will be configured, particularly if you are a freelancer. For example, you may know that a client is running Apache2 and PHP4, but you may not know beforehand if they have the appropriate PEAR libraries installed and on the include path. And if the code doesn't work, you look bad, no matter the reason. So consider bundling the required PEAR libraries alongside the scripts that use them, so you can guarantee that all the dependencies will be met. For convenience, this is how the code for this tutorial has been packaged.

Conclusion

At this point, further attempts to optimize the email contact form may result in diminishing returns. As the new subclasses created above are used more, limitations in their implementation will likely become apparent, such as when more complex element types are needed, and further improvements can be made to address these issues as needed.

What has been presented so far is what the author has found helpful in his particular use of this library and for his style of development. As the reader uses HTML_QuickForm, she may choose to integrate the library into her tool set in a different manner that better fits her needs and her development style. The author has merely tried to demonstrate one way in which, with a small investment of time and thought, robust libraries like HTML_QuickForm can be leveraged further in certain contexts.

Of course, there is much more to the HTML_QuickForm library than what has been shown in this tutorial. Complete documentation can be found at the HTML_QuickForm site, including this list of features:

The important thing to remember is that because the optimizations introduced in this tutorial were implemented by wrapping existing functionality in subclasses of HTML_QuickForm, none of the existing functionality in the original super class has been limited in any way. The goal has been to make the most commonly used functionality of HTML_QuickForm a little more convenient to use for developers with certain needs. But if you need to do more than these new methods allow, you can still use any of the documented HTML_QuickForm functionality, including the addElement and addRule methods, as needed, right alongside the new subclass methods introduced in this tutorial.

Additionally, there are links on the HTML_QuickForm site to other PEAR packages that integrate with and extend the functionality of HTML_QuickForm in various ways, including the creation of CAPTCHA elements, database integration, and the creation of multi-page forms. In other words, its unlikely that using HTML_QuickForm, or the subclasses presented here, will limit you in any way as your forms evolve into the future.

Resources

Adam Smith is a senior software architect and project leader at Cornell University in Ithaca New York, where he has been focused most recently on developing Cornell's digital repository and digital preservation infrastructure. He is also the founder of Agile Enterprise Software providing businesses with complete print-to-digital marketing solutions. Read Adam's blog at www.stonemind.net.


Return to ONLamp.

Copyright © 2009 O'Reilly Media, Inc.