Object Oriented PHP Forms

Related Articles

Introduction

First of all, we already know how to examine $HTTP_POST_VARS, we know how to name form inputs in such a way that they will be presented as a nice PHP array. Why do we need anything else at this stage?

Let me explain. You see, I have a Delphi background and with Delphi you get very much addicted to re-using instead of re-inventing. So, if you find yourself coding something that gives you this dejavu - “I've been doing this before” - you want to invent a class that can be reused throughout your projects.

Analysis

Let's take a look at the task of processing user forms and try to find the most common steps. They will describe generic functionality of a form. First of all, a form must be presented to the user. This includes rendering graphical representation of form objects (which is carried out by browsers quite efficiently). Then we must display the initial values; this should be governed by the application logic. Then the user's input must be received and analysed. We receive a set of values from the user. At the abstract level the decision that is made on this stage would usually be: can this form be considered completed or should we go back and ask user to correct something?

Let's first look at the situation when we need something corrected. In this case an application must display the form again and give some explanations about what went wrong (display some error messages). Besides, a well-mannered form should preserve values entered by the user, at least the correct ones.

Now suppose, we decide that the values are good enough to allow us to continue. Then these values (or some of them) usually get stored somehow (in a database, in session variables, etc.).

Finally, another decision is made: where do we go from here? It is often based on values entered and, of course, follows some paths defined by the programmer.

Now, what should we try to achieve by the object analysis? The best would be to program generic behaviour in parent class(es) once and forever. Then from that moment on we will be able to only concentrate on context-specific steps for each new form that we develop: check values, store values, decide where to go from the form.

The key element will be a generic form class that I called FormProcessor. Let's first get a general idea about how it works. I like putting forms into separate files. There's nothing that makes it necessary, it just makes things easier, for example, to edit them in visual editors. Also, I usually have forms that 'submit to themselves', in other words I usually group a form with the code that takes its input.

One idea is to create instances of FormProcessor's sub-classes in the beginning of such files. Then, this instance will 'tune in' the environment and will be able to have complete control over displaying the form and working with it further on. By 'tuning in', I mean deciding whether this particular form was displayed and submitted before and we should now check the input. If it has never been submitted before, it should be displayed for the first time. Object-oriented technology will work for us in both situations.

Generic framework within FormProcessor will run one of the virtual member functions -- either for checking or for displaying the form.

Notice: all we will have to do in each particular form's class is override these virtual functions. In the same way, we will override the function responsible for storing form's values. Again, the generic class will decide when to call it.

Executing Environment

Before we go deeper into details, there's one important thing to talk about. That is, how do we organize forms into a branching sequence (I hope, nobody minds me using the word 'wizard' here)?

Here's what I suggest. You may think of it as of some kind of a 'runner' that manipulates instances of classes derived from FormProcessor. This environment knows which form (script in a separate file) should be loaded first. It then includes the script containing a form (FormProcessor's descendant).

This form decides whether it is still displaying itself or just finishing processing of data submitted to it. After storing correct data it reports to the runner: “hey, I'm finished now - look, this is who should be next”.

Based on this information, the runner can load the next form, according to the decision made in the current one. The next form, when included, will sense that it is just loaded for the first time. It will notify the environment (runner) about that and the chain will stall at this point; Of course, waiting for the next form to be submitted.

Before something else distracts us, let's see what I meant here in the code:

<?php

/**
* A 'runner' - environment for managing form wizards
*
* @author Yuri Makassiouk <racer@bfpg.ru>
*/
//include('constants.php3');
//this is the directory where forms (wizard's screens) reside:
$wizardRoot = '';
if (!isset($wizardPage))
//initialize sequence. (This value should normally not be hard-coded)
$wizardPage = 'reg_form01.php';
clearstatcache();

while ($wizardPage) {
$page = ($wizardRoot . $wizardPage);
//echo ' ' . $wizardPage . ' '; // - uncomment this to monitor what's going on
$wizardPage = ''; // this will stall the wizard
if (file_exists($page)) {
// the included file may either set the global variable $wizardPage
// and that will continue the loop instantly
// or it may have the pair 'wizardPage=...' get submitted
// with a form the file contains.
include($page);
}
else {
echo "<br><b>Form wizard:</b> cannot find '$page', aborting wizard";
// this break isn't really necessary - no included file - nobody to
// set $wizardPage being != ''. But just in case, you know =)
break;
}
}

?>

Now, if you put the code above in a separate file, you may then include that file in your nicely formatted page. This page will be displaying your wizard's screens.

Implementing the FormProcessor Class

But this was still quite far from our original subject, we were talking about object-oriented forms here, remember? Let's have a look at FormProcessor and see what's under its hood.

I had to strip the code of PHPDoc-style comments, because I think it requires step-by-step explanations, while reading through the comments in arbitrary order might confuse. A version of this source with PHPDoc comments is available for download at the end of this article.

<?php

/**
* An abstract class implementing generic functionality for processing user's input
*
* This class encapsulates generic functions for working
* with data coming from user forms. Descendants must only override certain
* functions that perform context-specific tasks, like custom checking of
* data, storing correct data, etc.
*
* @author Yuri Makassiouk <racer@bfpg.ru>,
* Mission & Media <firma@missionandmedia.com>
*/
class FormProcessor {
var $Name;
var $FirstTime;
var $Values;
var $Errors = array();
var $ErrorMessageFormat = '<br>%s';
var $wizardPage;
var $fieldPrefix = 'mm_form_data_';

function FormProcessor($Name, $wPage='') {
$this->Name = $Name;
$this->wizardPage = $wPage;

$this->Values = $GLOBALS[$this->fieldPrefix.$this->Name];
$this->FirstTime = (count($this->Values) == 0);

if (!$this->FirstTime)
$this->CustomCheck();

if ($this->IsCompleted()) {
$this->StoreData();
$GLOBALS['wizardPage'] = $this->NextWizardPage();
}
else
$this->DisplayForm();
}

function IsCompleted() {
return (!$this->FirstTime && count($this->Errors)<=0);
}

function CustomCheck() {}
//abstract

function DisplayForm() {}
//abstract

function NextWizardPage() {}
//abstract

function StoreData() {}
//abstract

function Additional() {
if ($this->wizardPage) :
?>
<input type="Hidden" name="wizardPage" value="<?php echo $this->wizardPage?>">
<?php endif;
}

function Set($Name, $Value) {
$this->$Name = $Value;
}

function ErrorReport($Name) {
if (isset($this->Errors[$Name]))
printf($this->ErrorMessageFormat, $this->Errors[$Name]);
}

function GetInitialValue($Name) {
if (isset($this->Values[$Name]))
return $this->Values[$Name];
else
return false;
}

function InitialValue($Name) {
echo $this->GetInitialValue($Name);
}
}

?>

One of the key principles in FormProcessor's functionality is that it knows how the HTML form's inputs are named. Later on in this article, I will show how we automate naming of fields, now just take it as it is: FormProcessor's descendants know the name of the array that the form's values are submitted into. Inside the member functions of the class, this name is evaluated as:

$this->fieldPrefix . $this->Name

...where $this->Name is the form's name that we pass as constructor's parameter. $this->fieldPrefix is defined as a data member of the class, but you can think of it as of a constant value that remains the same throughout lifetime of instances of the class. Its only purpose is to transparently add 'uniqueness' to the field names, so that they do not accidentally get mixed with some of the program's other objects.

The first thing the generic constructor does is it 'smells the air around'. It assigns the global variable (whether it is set or not) with this magic name to $this->Values. Then, if $this->Values is not empty (i.e. form's fields were submitted before) FormProcessor decides that it is the not the first time this form is on the arena. In this case, it calls the abstract $this->CustomCheck() member function.

Overriding this function in sub-classes allows these classes' authors to implement whatever custom conditions checking they fancy. Of course, being a member function of the class gives CustomCheck() access to $this->Values array, which contains all values that form is working with as an associative array.

If overridden CustomCheck() finds error(s) that should not allow the form to be considered as completed, it should write error messages in $this->Errors data member. It is also treated as an associative array with the same association as $this->Values. In other words, each submitted value has a potential error message linked with it, which, if not empty, indicates a fault in this value.

Then, and this is my favorite thing about abstract generic classes, the rest of constructor reads like English: if the form is completed, let's somehow store the data it has. Otherwise, let's display it (again or for the first time - IsCompleted() returns 'false' if this is the form's first time).

Of course, StoreData() and DisplayForm() are calls to abstract member functions that should be overridden in sub-classes. I hope you have noticed that in case the form is completed, besides storing data, the constructor also assigns a new value to the global variable $wizardPage. This action tells the 'runner' - executing environment - what form it should load next (the 'while' loop in runner will make another iteration).

Can you guess where the next $wizardPage's value comes from? Of course, from an abstract function that should get overridden in a sub-class.

The meaning of the rest of the FormProcessor's machinery should become clear from the following discussion, and now, before we attempt to build something useful with our class, I have to explain how the HTML form's inputs know the names they should be assigned.

Inputs - Brief Introduction

In fact, auto-assigning inputs' names, really was the initial idea that pushed me towards developing of another set of classes. The classes that would serve as object wrappers for HTML form's input controls. In this article I would really like us to concentrate on the FormProcessor class and its descendants. Developing a properly analysed hierarchy of classes for form controls deserves a separate discussion. So, I suggest that we only talk about form controls classes as much as we need for completing this study.

<?php

/**
* A small hierarchy of classes that
* serve as object wrappers for HTML form's inputs
*/

/**
* An abstract class representing generic form control
*
* @author Yuri Makassiouk <racer@bfpg.ru>,
* Mission & Media <firma@missionandmedia.com>
*/
class FormControl {
var $Name;
var $form;
var $Value;
var $Attributes;
var $FormName;
var $InputName;

function FormControl($Aform, $AName, $AValue='', $AAttributes='') {
$this->Name = $AName;
$this->form = $Aform;
$this->Value =
($this->form->FirstTime)?$AValue:($this->form->Values[$this->Name]);
$this->Attributes = $AAttributes;
$this->FormName = $Aform->Name;
$this->InputName = sprintf("%s%s[%s]",
$this->form->fieldPrefix, $this->FormName, $this->Name);

$this->Display();
}

function InputName() {
echo $this->InputName;
}

function Display() {
echo $this->Render();
}

function Render() {}
//abstract
}

/**
* Class representing a text control
*
*/
class TextInput extends FormControl {

function Render() {
return "<input type="Text" name="".
$this->InputName."" value="$this->Value" $this->Attributes>";
}
}

/**
* Class representing a set of radio buttons
*
*/
class RadioButtons extends FormControl {
var $options;
var $ItemFormat = '%RBTN %LABEL';
var $separator = '<br>';

function RadioButtons($Aform, $AName,
$OptionList, $AValue='', $AAttributes='',
$AItemFormat='%RBTN %LABEL', $Aseparator = '<br>') {
$this->options = $OptionList;
$this->ItemFormat = $AItemFormat;
$this->separator = $Aseparator;
$this->FormControl($Aform, $AName, $AValue, $AAttributes);
}

function Render() {
$i=0;
$out = '';
while (list($key, $val)=each($this->options)) {
$item = $this->ItemFormat;
$item = str_replace('%RBTN',
sprintf("<input type="Radio" name="%s"
value="${key}" $this->Attributes%s>",
$this->InputName, $key==$this->Value?' checked':''), $item);
$item = str_replace('%LABEL', $val, $item);
if (++$i!=count($this->options))
$item .= $this->separator;
$out .= $item;
}
return $out;
}
}

?>

As you can see, the constructor of the base class (FormControl) does all the job of linking the control to form object, reference to which is passed as one of parameters. Then, the name of HTML input is evaluated. It should be such that the value will be submitted as part of an array which the form (descendant of FormProcessor) will be looking for. The name of input is then stored into a local data member. The control also receives the default value as one of constructor's parameters. This value will be used if the form that contains the control is displayed for the first time. The control is accessing a data member of form class to check this. Otherwise, the default value will be taken from the form's $Values array.

Then the constructor calls the Display() member function, which echoes output of the Render() function. This function is abstract and should be overridden in sub-classes so that they produce correct HTML code to represent themselves in pages.
See the definitions of the two other classes: TextInput and RadioButtons for an illustration of this.

Example of a Form

Now we are equipped for the final touch: building a small example. I will demonstrate creation of the first page of a wizard that should collect some information. Let's say, about a subscription to some internet resource. As the first step, the user must enter the username he/she is going to use and a valid email address, then select the type of the registration. Based on the registration type the user chooses, the wizard should proceed to a corresponding screen.

Before the wizard can proceed, it validates values that the user enters. I will not build the forms for the second step. There's not much interesting to do without introducing additional input classes; and I would still like the article's subject to stay on the same topic.

<?php

define('MinNameLength', 5);

class RegForm01 extends FormProcessor {

function CustomCheck() {
if (strlen($this->Values['Name']) < MinNameLength)
$this->Errors['Name'] =
'Username should contain at least ' . MinNameLength . ' symbols';

if ($this->Values['Email']) {
if (!eregi('^[-!#$%&'*+\./0-9=?A-Z^_`a-z{|}~]+
@[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+.[-!#$%&'*+
'\./0-9=?A-Z^_`a-z{|}~]+$', $this->Values['Email']))
$this->Errors['Email'] = 'Value entered does not
appear to be a valid e-mail address';
} else {
$this->Errors['Email'] =
'E-mail address is required to complete subscription';
}
}

function NextWizardPage() {
//decision about what wizard's screen to load next is made here,
//based on the submitted values:
return ($this->Values['PaymentMethod'] ==
'P')?'reg_form_free.php':'reg_form_payed.php';
}

function StoreData() {
// we will not discuss storing persistent variables in this article.
// here would normally appear code that updates a database or
// stores session variables
STORE_VARIABLE($this->Values['Name']);
}

function DisplayForm() {
// I'm not sure you meet a form like this on a real web-site =), but I hope
// it'll do for the example

?>
<form action="<?php echo $PHP_SELF?>" method="POST">
Username: <?php new TextInput($this, 'Name', '', 'size="30" maxlength="100"')?>
<?php $this->ErrorReport('Name')?>

Email: <?php new TextInput($this, 'Email', '', 'size="30" maxlength="100"')?>
<?php $this->ErrorReport('Email')?>
<br><br>
Subscription type:<br>
<?php new RadioButtons($this, 'SubscrType', array('P'=>'Preview', 'M'=>'Membership'), 'P')?>
<?php $this->ErrorReport('SubscrType')?>
<br>
<br>
<?php $this->Additional()?>
<input type="submit" name="Submit" value="Submit">
</form>
<?php

}
}

$form = new RegForm01('RegForm01', 'reg_form01.php');
?>

This code can also be downloaded at the end of this article.

Conclusion

Object-oriented programming is an efficient and powerful technique. It allows one to create reusable, easily adjustable components that make programming process fast and, honestly, very much fun too. I hope my little experience, that I presented here, will inspire you to build your own libraries of classes that would be a base for complex and sophisticated web applications.


Publication Date: Tuesday 29th July, 2003
Author: Yuri Makassiouk View profile

Related Articles