Simplifying Automated Form Generation, Validation, and Output

First Praise, Then Critique

HTML_QuickForm is the standard class within PEAR for building forms programmatically. Here's an example:


$form =& new HTML_QuickForm();
$type = 'text';
$name = 'city';
$label = 'Your City:';
$form->addElement($type, $name, $label);

(All XHTML element types are supported, including some pseudo-types such as 'date' and 'time').

When you call $form->display(), the class builds all the HTML for the form and the elements you have added. That's just the start, though. If you have a POST variable called 'city', it will populate the form with that value for you. You can add validation methods to each element, and the class will check the values and print error messages inside the form if the values are not valid. You can apply special processing to the values before retrieving them as an array with $form->export().

It's quite a neat package, but I have some quibbles with its operation. These are not design flaws; indeed, its design seems quite internally consistent. For example, each element is represented internally as an object itself, with its own methods and properties (usually descended from a base element class). For example, to set the value of an individual element, you have to call its setValue() method -- but any submitted values (e.g., in POST) will override that value. There are ways around this, but none of them seem simple or straightforward.

More to the point, while I am not a strict model-view-controller devotee, I find that HTML_QuickForm not only pierces the veil between model and view (or controller and view, depending on your definition), it positively shreds that veil to pieces. Yes, it has renderers that allow you to parse through the internal objectified representatiion of the form elements, but they system seems overly complex.

While I like HTML_QuickForm, and have written code (in DB_Table) that automatically generates forms from tables using it, I don't fully comprehend the methodologies underlying its operation. It seems to me there should be a simpler and more straightforward way of automating form generation, one that is both more comprehensible and more open to MVC separate while retaining the obvious strengths of validation and automated population of values.

Savant and Solar

Note: Savant3 and Solar are publicly available but are still in development. I present the remainder of this article as advance information on how they will interoperate, and while I am using the described methodologies on a continuing basis, the specifics may change in the future.

With the above points in mind, I developed a plugin for Savant2 that accepts an array of element definitions and generates forms from it (called Savant2_Plugin_form for obvious reasons). In Savant3, to be released, I have refined and extended that original idea to be a bit more full-featured (and I intend to backport it to Savant2, so don't worry about missing out). The Savant plugins cover the 'view' half of form generation. For the 'model' (or the 'controller' depending on how you think about these things) portion, I have implemented a complementary system in Solar_Form to collect and manage these element arrays.

The key here is that the form generation happens not in one location, as in HTML_QuickForm, but in two: Solar_Form defines the form elements, and Savant renders it. One benefit of this separation of duties is that neither class cares what the other class is. If you like, you can send a hand-built array of elements to Savant, and it will build the form just the same. Similarly, you can pass a Solar_Form element array to any template system, and if your template system knows how to deal with that array, it can render a form too. The point is that the information about the form is transmitted in a standard format as an array.

Therefore, the format of the element array is of paramount importance. Lucky for us, it is quite simple. For each element in the form, you have an array that looks like this:


$element = array(

    // the 'name' attribute for the XHTML form element
    'name'     => null,

    // the 'type' attribute
    'type'     => null,

    // a text label for the element
    'label'    => null,

    // the 'value' attribute of the element
    'value'    => null,

    // whether or not the element is required
    'require'  => false,

    // whether or not the element is disabled
    // (i.e., 'read-only')
    'disable'  => false,

    // any options related to the element values,
    // typically for select options, radios, or
    // checkbox values
    'options'  => array(),

    // additional attributes for the XHTML form element
    'attribs'  => array()
);

From our earlier example above, you would create an element under this system simply by specifying the array keys for it:


$element = array(
    'name' => 'city',
    'type' => 'text',
    'label' => 'Your City:',
    'value' => 'Memphis'
);

The Savant3 template system can take this array and render a form within a template script like this.


// assume that $element has been assigned as 'elem'
$this->form('start');
$this->form('auto', $this->elem);
$this->form('end');

Note the use of the 'auto' call, which automatically builds a single form element from an array; if you use the 'fullauto' call, it will build multiple elements from a sequential array of elements, in order.

The above discussion only describes the manual generation of form elements (the display is automated via Savant3). To get more of the functionality of HTML_QuickForm, we would need to use Solar_Form.

Solar_Form collects an array of elements together in a class property; you can add them one-by-one like this:


$form = Solar::object('Solar_Form');
$element = array(
    'type' => 'text',
    'label' => 'Your City:',
    'value' => 'Memphis'
);

// note that the 'name' key is optional when
// using setElement(); Solar_Form will use the
// first parameter as the 'name' value, overriding
// the 'name' key.  this has benefits when
// building arrays of elements.
$form->setElement('city', $element);

To populate the form elements with their related POST values, simply call the populate() method.


$form->populate();

You can then pass that form to Savant3 ...


$Savant3->form_elements = $form->elements;

... and render the form in a template script like so:


$this->form('start');
$this->form('fullauto', $this->form_elements);
$this->form('end');

Finally, you can add automated validation to the Solar_Form elements with the addValidate() method. Below, the $method relates to a standard Solar_Valid method; in addition to 'alpha', there are 'alphanumeric', 'numeric', 'isoDate', 'email', and other validation methods, as well as 'regex' (for user-defined regular expressions), 'custom' (for call_user_func callbacks), and 'multiple' (for arrays of validation routines).


$form = Solar::object('Solar_Form');

$element = array(
    'type' => 'text',
    'label' => 'Your City:',
    'value' => 'Memphis'
);

$form->setElement('city', $element);

$name = 'city';
$method = 'alpha';
$feedback = 'Please use only letters in the city name';
$form->addValidate($name, $method, $feedback);

Then you can call the validate() method, which will return true or false, and will automatically set the feedback messages for elements that failed validation.


if ($form->validate()) {
    $values = $form->values();
    // insert values into a table
}

$Savant3->form_elements = $form->elements;

Conclusion

While I shamelessly promote Solar and Savant throughout this entry, my main goal is to point out that MVC separation of form building, validation, automated population, and output display is possible with a unified format describing the form elements. It is not necessary to wrap all those functions into one class; indeed, separating the concerns seems vital to extended development cycles when differing framework systems must interoperate. Having it all in one class is great to start with, but after a while you find yourself coding to the class and trying to learn its intricacies, instead of using it for your own purposes.

Are you stuck with a legacy PHP application? You should buy my book because it gives you a step-by-step guide to improving you codebase, all while keeping it running the whole time.