Forms Package

Provides an OO environment in which to build, validate and render HTTP forms. Forms are built as abstract containers specifying input types and validation rules, and can either be rendered using custom markup or by standard visitor renderers. Supports file uploads, hidden inputs, and automatic CSRF protection.

Defining A Form

To use a form, you must first define it's structure. This is achieved by creating a parent form composite object, and adding all the elements that are required as children. The form object then encapsulates a specification for the user input that is required. For example, to build a skeleton enquiry form with name, email and message fields:

  1. <?php
  2. $form = new T_Form_Post('enquiry','Send Message');
  3. $form->addChild(new T_Form_Text('name','Name'));
  4. $form->addChild(new T_Form_Text('email','Email'));
  5. $form->addChild(new T_Form_Text('subject','Subject'));
  6. $form->addChild(new T_Form_TextArea('msg','Message'));
  7. ?>

The form object is not the only composite available, other composites like fieldsets can be used to build a tree that includes nested children elements. For example, if we introduced fieldset grouping to the above example:

  1. <?php
  2. $form = new T_Form_Post('enquiry','Send Message');
  3. $set=new T_Form_Fieldset('contact','Contact Details')
  4. $form->addChild($set);
  5. $set->addChild(new T_Form_Text('name','Name'));
  6. $set->addChild(new T_Form_Text('email','Email'));
  7. $set=new T_Form_Fieldset('data','Your Enquiry')
  8. $form->addChild($set);
  9. $set->addChild(new T_Form_Text('subject','Subject'));
  10. $set->addChild(new T_Form_TextArea('msg','Message'));
  11. ?>

Optional Fields

By default, elements are required rather than optional inputs, so in our enquiry form above all the elements defined must be submitted for the form to validate. To create an optional input, use the setOptional() method on the element. e.g. to make message subject an optional input:

  1. <?php
  2. // ... [snip] ...
  3. $subject = new T_Form_Text('subject','Subject');
  4. $subject->setOptional();
  5. $set->addChild($subject);
  6. // ... [snip] ...
  7. ?>

Adding Validation Rules

In our current enquiry form we have defined that certain inputs must be available, but we have not yet defined any rules that this input data must meet. We do this by attaching input validation filters to the elements. Some standard filters are available in the library as the T_Validate_* family of classes.

  1. <?php
  2. $email = new T_Form_Text('email','Email');
  3. $email->attachFilter(new T_Validate_Email);
  4. ?>

Custom Validation Rules

There are a number of standard validation filters defined within the library itself for convenience, but it is easy to define your own validation rules. Validators are written as T_Filter classes, and failures should result in a T_Exception_Filter being thrown. For example, a custom validation class based on a particular regex might look like:

  1. <?php
  2. class MyCustomValidator implements T_Filter
  3. {
  4. function transform($value)
  5. {
  6. $regex = '/some regex/'
  7. if (!preg_match($regex,$value)) {
  8. throw new T_Exception_Filter('must be in the format XXXX');
  9. }
  10. return $value;
  11. }
  12. }
  13. ?>

Filters Are Not Just For Validation!

Filters act on the data submitted and although they can act as validators by throwing T_Exception_Filter exceptions, they can also/instead transform the input data that is submitted. For example to make sure the email address submitted is not only valid, but also to transform it to lower case:

  1. <?php
  2. $email = new T_Form_Text('email','Email');
  3. $email->attachFilter(new T_Validate_Email)
  4. ->attachFilter('mb_strtolower');
  5. ?>

Element Attributes

There are set and get methods for additional element attributes, for example it can be used to set ID values for javascript to target, or to set textarea dimensions:

  1. <?php
  2. $msg = new T_Form_TextArea('msg','Your Message');
  3. $msg->setAttribute('rows',7)
  4. ->setAttribute('cols',60);
  5. ?>

Setting some attributes can also add validation filters internally. For example setting the maxlength attribute on a text field also adds a validation filter to enforce the max length restriction server-side.

  1. <?php
  2. $name = new T_Form_Text('name','Name');
  3. $name->setAttribute('maxlength',100);
  4. ?>

Selects, Radios and Checkboxes

List elements such as selects (T_Form_Select), radio options (T_Form_Radio) or checkboxes (T_Form_Checkbox) have the same interface as the text elements we have already seen with the addition of a setOptions() method that is used to set the available options. The first argument to this method is an array of value=>name pairs, and an optional second argument is the not present option.

  1. <?php
  2. $title = new T_Form_Select('title','Title');
  3. $options = array('Mr','Mrs','Ms','Miss','Dr');
  4. $title->setOptions($options,'Select your title');
  5. ?>

While both select and radio elements encapsulate a scalar response, the checkbox element encapsulates an array of selected elements from the options provided. All these elements check that the response is from the list of options provided during validation.

Setting Element Defaults

Element defaults can be set using the setDefault() method of any element. For list elements (select, radio, checkbox), the default(s) must be in the previously set list of options.

  1. <?php
  2. $name = new T_Form_Text('name','Name');
  3. $name->setDefault('Joe Bloggs');
  4. $lang = new T_Form_Checkbox('lang','Your Languages');
  5. $options = array('php'=>'PHP','py'=>'Python','rails'=>'RoR');
  6. $lang->setOptions($options)
  7. ->setDefault(array('php','py'));
  8. ?>

File Uploads

File uploads can be included in a form using the T_Form_Upload element. By default any file can be uploaded, and a T_File_Upload object is returned once the element is validated. To restrict what files can be uploaded, we use validation filters in a similar way to other elements. For example, the T_Validate_UploadMime filter can be used to restrict the file types that can be uploaded.

  1. <?php
  2. $doc = new T_Form_Upload('doc','Document');
  3. $permitted = array('doc','docx','txt','xls','xlsx');
  4. $upload->attachFilter(new T_Validate_UploadMime($permitted));
  5. ?>

Often uploaded files are images, and a number of validation filters specifically exists for this purpose. For example, an image upload that must be an image (PNG, GIF or JPEG permitted) which is at least 200px wide and no wider than 4000px:

  1. <?php
  2. $img = new T_Form_Upload('img','Image');
  3. $img->attachFilter(new T_Validate_ImageGdUpload)
  4. ->attachFilter(new T_Validate_ImageWidthRange(200,4000));
  5. ?>

Hidden Elements

Hidden values can be included in forms using the T_Form_Hidden element. This element includes the specified value in the form, along with a hashed checksum that guarantees that the elemnt will only validate on submission if the origianl value has not been tampered with.

  1. <?php
  2. $timeout = new T_Form_Hidden('timeout',time()+15*60);
  3. ?>

Rendering a Form

A form composite encapsulates the business logic of a user input -- what is required, and what rules it must obey -- but has no knowledge as to how the form might be rendered. Instead, it provides an accept() method to pass in a visitor that does encapsulate such logic. By default, a XHTML form renderer is provided:

  1. <?php
  2. $form = new T_Form_Post('enquiry','Send Message');
  3. $form->addChild(new T_Form_Text('name','Name'));
  4. $form->addChild(new T_Form_Text('email','Email'));
  5. $xhtml = new T_Form_Xhtml;
  6. $form->accept($xhtml);
  7. echo $xhtml;
  8. ?>

The T_Form_Xhtml class is designed to be extended into your own form renderer for your application, and provides various hooks to insert HTML content around the elements. For example, a form renderer that displays a form with elements in an ordered list can be achieved with:

  1. <?php
  2. class MyFormView extends T_Form_Xhtml
  3. {
  4. protected function preGroup($node)
  5. {
  6. $this->changeIndent(1);
  7. return $this->indent.'<ol>'.EOL;
  8. }
  9. protected function preLabel($node)
  10. {
  11. $this->changeIndent(1);
  12. $xhtml = $this->indent.'<li>'.EOL;
  13. $this->changeIndent(1);
  14. return $xhtml;
  15. }
  16. protected function postElement($node)
  17. {
  18. $this->changeIndent(-1);
  19. $xhtml .= $this->indent.'</li>'.EOL;
  20. $this->changeIndent(-1);
  21. return $xhtml;
  22. }
  23. protected function preNestedFieldset($node)
  24. {
  25. $this->changeIndent(1);
  26. $xhtml = $this->indent.'<li>'.EOL;
  27. $this->changeIndent(1);
  28. return $xhtml;
  29. }
  30. protected function postNestedFieldset($node)
  31. {
  32. $this->changeIndent(-1);
  33. $xhtml = $this->indent.'</li>'.EOL;
  34. $this->changeIndent(-1);
  35. return $xhtml;
  36. }
  37. protected function postGroup($node)
  38. {
  39. $xhtml = $this->indent.'</ol>'.EOL;
  40. $this->changeIndent(-1);
  41. return $xhtml;
  42. }
  43. }
  44. ?>

Separate renderers can perform different rendering tasks on a form. For example T_Form_XhtmlError simply renders a list of the form errors that could be placed at the top of an HTML page. These visitors provide a general way for you to easily write re-usable form rendering rules, which can be used on the whole form, or partial parts of it (depending what you call accept on).

Performing Form Validation

Validating the form or parts of the form is achieved using the isSubmitted() and validate() methods. Both of these functions take the data to validate as an argument in the form of a T_Cage_Array. T_Cage_Array user input objects can be retrieved from the code environment or built directly from the superglobals:

  1. <?php
  2. $env = new T_Environment_Http;
  3. $post = $env->input('POST');
  4. // ... OR ...
  5. if (get_magic_quotes_gpc()) {
  6. $f = new T_Filter_NoMagicQuotes;
  7. $post = new T_Cage_Array($f->transform($_POST));
  8. } else {
  9. $post = new T_Cage_Array($_POST);
  10. }
  11. ?>

Forms are validated using the validate() method and this executes the validation assuming the form has been submitted. To check whether a form has been submitted (if you have multiple forms on the same page), the isSubmitted() method can be used. Once validated the methods isPresent() and isValid() can be used to check if a submission if present and valid.

  1. <?php
  2. $env = new T_Environment_Http;
  3. $form = new T_Form_Post(/* snip */);
  4. // ... build form ...
  5. $post = $env->input('POST');
  6. if ($env->isMethod('POST') && $form->isSubmitted($post)) {
  7. $form->validate($post);
  8. }
  9. if ($form->isPresent() && $form->isValid()) {
  10. // action form..
  11. } else {
  12. // display form
  13. }
  14. ?>

Note that until a form fails validation it is regarded as being in a valid state (so isValid() returns true). Thus to check whether a form has been submitted and is valid you must make sure both that the form isPresent() and isValid().

Using Form Results

Specific elements can be retrieved from the form composite via their alias using the search() method. Elements also support the isPresent() and isValid() method individually and getValue() can be used to retrieve the filtered value.

  1. <?php
  2. // .. build form, and validate ..
  3. // retrieve and action results
  4. $email = $form->search('email')->getValue();
  5. $tel = $form->search('tel');
  6. $tel = $tel->isPresent() ? $tel->getValue() : 'none';
  7. // ... etc.
  8. ?>

Delving Deeper...

Shortly, there will be some How-Tos posted about:

  • How to use forms with controllers
  • Avoiding CSRF vunerabilities
  • Building repeatable form blocks and multi-step forms.

Other packages include ACL, Client, Controllers, Core, DB, I18n, Reflection, Unit, Views, Wiki.