Overview of QuickForm2_Controller

Overview of QuickForm2_Controller – Easy building of multipage forms

What is HTML_QuickForm2_Controller?

If you are already familiar with MVC frameworks, then you probably know: controller is a component that accepts user input and instructs other components to perform actions based on that input. HTML_QuickForm2_Controller is somewhat smaller in scope than a typical framework controller since it only deals with forms. When using the Controller, an action name is sent in GET or POST data, usually by clicking a specially named button. This name contains id of one of the form pages added to Controller and name of action handler to call (e.g. 'next' for going forward in a wizard), that data is extracted and an appropriate action handler is called.

Key features:

  • Allows forms spanning multiple pages, stores form data in session between HTTP requests;
  • Binds OO action handlers to buttons that submit the form;
  • Includes default action handlers that allow easy building of multipage forms;
  • Even if you only use it for single-page forms, form-related logic is kept in classes rather than in procedural code.

Key classes and interfaces:

HTML_QuickForm2_Controller
Extracts the action name from request and calls the appropriate handler. Contains several Pages and a wrapper for values stored in session.
HTML_QuickForm2_Controller_Page
Class representing a single page of the form. Contains an instance of HTML_QuickForm2.
HTML_QuickForm2_Controller_Action
Action handlers implement this interface.

HTML_QuickForm2_Controller is a rewrite of PHP4 HTML_QuickForm_Controller, so if you are already using the latter you can go to migration guide which contains step-by-step instructions for porting your scripts to new package.

Basic usage example

Session initialization

This simple example does not use sessions since there is no need to pass data between pages. You'll need to use sessions when dealing with a real multipage form, though. HTML_QuickForm2_Controller does not start a session automatically, you should explicitly call session_start() before instantiating the controller class.

More complex usage examples are installed with the package. Along with a form similar to the one below they include two multipage forms: a tabbed form and a wizard.

The same example form that was used in the package's tutorial will now be rewritten using the Controller:

Builds and processes a form with a single input field, using Controller

<?php
// Load the main class
require_once 'HTML/QuickForm2.php';
// Load the controller
require_once 'HTML/QuickForm2/Controller.php';
// Load the Action interface (we will implement it)
require_once 'HTML/QuickForm2/Controller/Action.php';

// Class representing a form page
class TutorialPage extends HTML_QuickForm2_Controller_Page
{
    protected function 
populateForm()
    {
        
// Add some elements to the form
        
$fieldset $this->form->addElement('fieldset')->setLabel('QuickForm2_Controller tutorial example');
        
$name $fieldset->addElement('text''name', array('size' => 50'maxlength' => 255))
                         ->
setLabel('Enter your name:');

        
// We set the name of the submit button so that it binds to default 'submit' handler
        
$fieldset->addElement('submit'$this->getButtonName('submit'),
                              array(
'value' => 'Send!'));
        
// The action to call if a user presses Enter rather than clicks on a button
        
$this->setDefaultAction('submit');

        
// Define filters and validation rules
        
$name->addFilter('trim');
        
$name->addRule('required''Please enter your name');
    }
}

// Action to process the form after successful validation
class TutorialProcess implements HTML_QuickForm2_Controller_Action
{
    public function 
perform(HTML_QuickForm2_Controller_Page $page$name)
    {
        
$values $page->getController()->getValue();
        echo 
'<h1>Hello, ' htmlspecialchars($values['name']) . '!</h1>';
    }
}

$page = new TutorialPage(new HTML_QuickForm2('tutorial'));
// We only add the custom 'process' handler, Controller will care for default ones
$page->addHandler('process', new TutorialProcess());

$controller = new HTML_QuickForm2_Controller('tutorial');
// Set defaults for the form elements
$controller->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
    
'name' => 'Joe User'
)));
$controller->addPage($page);
// Process the request
$controller->run();
?>

You may note that the above code is more verbose than the original. While that is definitely true, to make a three page wizard you'll only need to create three subclasses of HTML_QuickForm2_Controller_Page, a 'process' handler and add them to Controller, which already has default handlers for typical 'Back' and 'Next' buttons. It will require a non-trivial amount of programming without the Controller infrastructure.

Creating form pages

To define pages for your controller you need to subclass HTML_QuickForm2_Controller_Page implementing its abstract populateForm() method. Note that this method will be called on demand (i.e. only when the user sees the form in question), so it is better performance-wise to keep all form-building code in it than to pass a pre-populated instance of HTML_QuickForm2 to page's constructor.

Most of the code in the method just repeats what was done in the original tutorial, however some of it requires explanation:

<?php
// We set the name of the submit button so that it binds to default 'submit' handler
$fieldset->addElement('submit'$this->getButtonName('submit'),
                      array(
'value' => 'Send!'));
// The action to call if a user presses Enter rather than clicks on a button
$this->setDefaultAction('submit');
?>

The first line uses getButtonName() to set a special name for form's submit button and thus trigger a 'submit' action on a 'tutorial' form when that button is clicked (default handler for such action is implemented in HTML_QuickForm2_Controller_Action_Submit). The second one sets an action to trigger when no submit button is clicked (e.g. user presses Enter instead of clicking). Under the hood this is done by adding an instance of HTML_QuickForm2_Controller_DefaultAction to the form.

Creating custom action handlers

You'll usually need custom handlers for only two actions:

'process'
Called when the form is submitted and passes validation. In case of multipage forms that means that all the pages are valid.
'display'
Called when a form page needs to be displayed. It does have a default handler, but usually you'll want to tweak the output.

'process' handler is, obviously, completely application-specific and will usually deal with storing the form values somewhere. When creating a custom 'display' handler it is easiest to subclass HTML_QuickForm2_Controller_Action_Display and override its renderForm() method to use a Renderer or do any other tweaks you like.

Setting up the controller

First we instantiate the custom Page class and add a custom action handler to it

<?php
$page 
= new TutorialPage(new HTML_QuickForm2('tutorial'));
// We only add the custom 'process' handler, Controller will care for default ones
$page->addHandler('process', new TutorialProcess());
?>

We don't bother with other action handlers as Controller will automatically load and use them.

Then we instantiate the controller

<?php
$controller 
= new HTML_QuickForm2_Controller('tutorial');
?>

Note that Controller needs an id and it should be unique if you have several Controllers in application, as it is used for storing values in session.

Then we add a DataSource and the page to the Controller

<?php
// Set defaults for the form elements
$controller->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
    
'name' => 'Joe User'
)));
$controller->addPage($page);
?>

While it is possible to add DataSource to an instance of HTML_QuickForm2 within Page instead, the above code allows setting the default values for the whole (possibly multipage) form. Note also that Controller's DataSources, unlike form's, are stored in session.

Finally we call the Controller's run() method

<?php
$controller
->run();
?>

which takes care of finding the name of the current action and calling the necessary handler.