View Source

<ac:macro ac:name="info"><ac:parameter ac:name="title">Proposal has been revised in-depth on 14 October 2006. Please take a look at revision 0.2 thoroughly if you have already read and commented revision 0.1</ac:parameter></ac:macro>

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFDEV:Zend Proposal Zone Template}

{zone-data:component-name}
Zend_Form
{zone-data}

{zone-data:proposer-list}
[Simon Mundy|mailto:studio@peptolab.com]
[Ralf Eggert|mailto:r.eggert@travello.de]
{zone-data}

{zone-data:revision}
0.1 - 28 August 2006: Initial proposal.
0.2 - 14 October 2006: revised proposal.
{zone-data}

{zone-data:overview}
Zend_Form is a component for creating, validating and processing form data.
{zone-data}

{zone-data:references}
* [PEAR_QuickForm|http://pear.php.net/package/HTML_QuickForm]
* [Zend_View form helpers|http://framework.zend.com/manual/en/zend.view.helpers.html]
* [patForms|http://trac.php-tools.net/patForms]
* [HTML_QuickForm2|http://quickform.mamasam.com/wiki/]
* [Complete XForms Examples|http://www.w3.org/TR/xforms/sliceG.html]
* [XForms for HTML Authors|http://www.w3.org/MarkUp/Forms/2003/xforms-for-html-authors.html]
{zone-data}

{zone-data:requirements}
* One or multiple instances per page
* Provides an interface for creating forms for a variety of end-uses (XHTML for current proposal, other end-uses to be implemented)
* Allows auto or manual data population (can utilise Zend_Filter_Input)
* Allows fine-grain control of form optional parts for display, validation, filtering and generation
{zone-data}

{zone-data:dependencies}
* Zend_Exception
* Zend_Input_Filter
* Zend_Filter (tbc)
* Zend_Validator (tbc)
{zone-data}

{zone-data:operation}

Zend_Form provides a set of loosely coupled parts and view helpers to model, validate & filter, display, generate and process a form. The core of Zend_Form is the form model which is the only mandatory part. All other parts are optional, yet all parts work together smoothly with the form model.

h3. Form model

With the form model the programmer will be able to model a specific form. It is an abstract class which needs to be extended to implement a specific form model. The model class provides the required functionalities to:

* add the unique id for the form
* add form fields
* set defaults for each form field
* accept values for the form fields (e.g. from raw $_POST data, a Zend_Filter_Input object or even a simple array)
* pass all form attributes, values and fields to an array

It also provides an abstract setup method which needs to be implemented in an extended form model class to add fields to the specific form.

Every form field is an object with a name property, an unset Zend_Validator property, an unset Zend_Filter object and a simple isValid() method.

h3. Validation and Filtering

Since the validation and filtering part is optional the programmer can use his own validation method. Anyway the validation and filtering part provides the required functionalities to:

* add a Zend_Validator object with validation rules (e.g. Zend_Rule_Range) to a field of the form
* add a Zend_Filter object with filter rules (e.g. Zend_Filter_Int) to a field of the form
* add allowed keys for a selection using a Zend_Rule_IsIn validation rule
* validate the form
* get the error status of invalid form fields

Based on the added Zend_Validator objects a form renderer will get some relevant information about how to display a specific form field, e.g. to display the "maxlength" attribute for text input fields or to specify if a select takes only one or more options. Also the allowed select options will be set with validation rules (Zend_Rule_IsIn).

h3. Extended form fields

There are a set of form field objects which extend the basic Zend_Form_Field. They do not add much more functionality to Zend_Form_Field except setting up the Zend_Validator object for the form field with a set of validation rules and setting up the Zend_Filter object with a set of filter rules.

For example the Zend_Form_Field_Text sets up the Zend_Validator object with the Zend_Rule_String validation rule. Optional it takes a range for the text field and adds a Zend_Rule_Range validation rule to the Zend_Validator. Selections which expect only one option to be selected, can be handled by Zend_Form_Field_Enum. Selections which expect none, one or more options to be selected can be handled by Zend_Form_Field_Set.

Zend_Form_Field can easily be extended by the programmer to add custom validation and filter rules.

h3. View helpers

The view helpers are optional as well and extend the currently available view helpers by taking a form object to generate the output (e.g. XHTML) for

* text fields
* password fields
* textareas
* checkboxes
* radio buttons
* select lists
* file upload fields

All view helpers take a reference to an field of the form object to identify values and other attributes set by the form object. The view helpers also examine the attached Zend_Validator object for the output (e.g. to set the "maxlength" for text inputs). Furthermore the view helpers take a list of other attributes for displaying the form field (e.g. "class", "size", "rows", "title").

For a select list, checkboxes and radio buttons the view helper will add the keys and corresponding values to display. Enum selections can be display as single selects or radio buttons. Set selections can be displayed as multiple selects or checkboxes.

To display validation errors there is a view helper to get an array of all unmatched validation rules for a specific form field or the whole form. Based on this the appropiate errors messages can be displayed.

*Note*: it still needs to be discussed if the current view helpers (release 0.1.5) should be kept or overwritten.

h3. Automatic rendering

Again the rendering module is optional, so the programmer can handle the rendering of the form in every way he wants to, for example with the view helpers mentioned above. The rendering module will provide the required functionalities to

* take further display attributes for each form field
* organize form fields into sections (fieldsets)
* generate the output code (e.g. XHTML) for a single field
* generate the output code (e.g. XHTML) for the complete form

*Note*: the layout of these generated forms will mostly be limited to a linear layout but can be changed with CSS.

h3. Automatic form generation

This optional module generates a form model with all fields from a record of a table (Zend_Db_Table_Row) or from a XML data record. Also some validation rules will automatically be added when appropriate, e.g. set maxixum chars, allowed keys for selections, etc.).

The generated form class can be cached or saved and extended with additional validation and filter rules.

h3. Form processor

This optional module accepts validated form data and processes it according to business needs (*Note*: Darby, please specify in more detail, what a form processor should do and how it should do it).

h3. Form controller

This optional part extends the Zend_Controller_Action to add a set of methods to handle form building, form validation, form rendering and form processing and can be extended to build specific controllers.
{zone-data}

{zone-data:milestones}
* Milestone 1: Form building and form rendering with Zend_View helpers (UC 01, UC 02)
* Milestone 2: Render form completely and render form to an array (UC 03, UC 04)
* Milestone 3: Form validation and filtering with Zend_Validator and Zend_Filter (UC 05, UC 06, UC 07); NOTE: can not be completed until Zend_Validator and Zend_Filter can be used!
* Milestone 4: Automatic form generation (based on a database table or an xml file) and form processing (UC 08, UC 09)
* Milestone 5: Improve the client side of the form handling with Javascript validation, AJAX interaction and more.
* Milestone 6: Handle Xforms
{zone-data}

{zone-data:class-list}
h3. Form model

* Zend_Form
* Zend_Form_Exception (extends Zend_Exception)
* Zend_Form_Field
** Zend_Form_Field_Text
** Zend_Form_Field_Enum
** Zend_Form_Field_Set
** Zend_Form_Field_File

h3. View helpers

* Zend_View_Helper_FormElement (acts as a factory to a Zend_Form_Render object)

h3. Automatic rendering

* Zend_Form_Renderer_Abstract
** Zend_Form_Renderer_Xhtml
*** Zend_Form_Renderer_Xhtml_Input
*** Zend_Form_Renderer_Xhtml_Text
*** Zend_Form_Renderer_Xhtml_Select
*** Zend_Form_Renderer_Xhtml_Textarea
*** Zend_Form_Renderer_Xhtml_Checkbox
*** Zend_Form_Renderer_Xhtml_Radio
*** Zend_Form_Renderer_Xhtml_Button
*** Zend_Form_Renderer_Xhtml_Hidden
*** Zend_Form_Renderer_Xhtml_File
** Zend_Form_Renderer_Xform

h3. Automatic form generation

* Zend_Form_Generator_Abstract
** Zend_Form_Generator_Db
** Zend_Form_Generator_Xml

h3. Form processor

* Zend_Form_Processor
{zone-data}

{zone-data:use-cases}

The use cases will build a simple form with the following form elements:

* 'username': an input type=text for username (minlength = 5, maxlength = 32)
* 'password': an input type=password for password (minlength = 5, maxlength = 16)
* 'email': an input type=text for email
* 'language': a single select for a language selector with an associative array ('en' => 'English', 'oz' => 'Strine', 'nz' => 'Kiwi');
* 'accept': a checkbox for site acceptance

{composition-setup}
{deck:id=Usecase}
{card:label=UC 01 : Simple usage}
h3. UC 01: Use the form model with your own home made validation methods

{code}
class User_Form extends Zend_Form
{
protected $_defaults = array('lang' => 'oz', 'accept' => 'on')

protected function _setup()
{
$this->add('username');
$this->add('password');
$this->add('email');
$this->add('language');
$this->add('accept');
}
}

$form = new User_Form('my_user_form');
$form->populate($_POST);

if (MyAwesomeValidator::validate($form)) {
$values = $form->export();

// process data, e.g. save to database
}
{code}
{card}

{card:label=UC 02 : View helpers}
h3. UC 02: Template view that renders the form using view helpers

{code}
$form->setRenderer(new Zend_Form_Renderer_Xhtml());
$view = new Zend_View();
$view->form = $form;
$view->render('user_form.tpl');
{code}

{code}
<h1>Enter user details</h1>
<form id="<?php echo $this->form->getId() ?>" method="post" action="<?php echo $this->form->getActionPath() ?>">
<fieldset>
<legend>Enter personal data</legend>
<p><label for="username">Username</label>
<?php echo $this->formElement('text', $this->form->username, array('size' => 32)) ?></p>
<p><label for="password">Password</label>
<?php echo $this->formElement('password', $this->form->password, array('size' => 32)) ?></p>
<p><label for="email">Email</label>
<?php echo $this->formElement('text', $this->form->email, array('size' => 32)) ?></p>
</fieldset>
<fieldset>
<legend>Enter settings</legend>
<p><label for="languages">Language</label>
<?php echo $this->formElement('select', $this->form->language, array('class' => 'half')) ?></p>
<p><label for="accept">Accept the terms?</label>
<?php echo $this->formElement('checkbox', $this->form->accept) ?></p>
</fieldset>
<p><?php echo $this->formElement('submit', $this->form->submit) ?></p>
</form>
{code}
{card}

{card:label=UC 03 : Auto rendering}
h3. UC 03: Automatic rendering

{code}
// create and validate form
$form = new User_Form('my_user_form');
$form->populate($_POST);

if ($form->validate()) {
$values = $form->export();

// process data, e.g. save to database
}

// instantiate renderer and add further attributes and options for rendering
$renderer = new Zend_Form_Renderer_Xhtml($form);
$renderer->setType('username', 'text')->setAttributes('username', array('label' => 'Username', 'size' => 32));
$renderer->setType('password', 'text')->setAttributes('password', array('label' => 'Password', 'size' => 32));
$renderer->setType('email', 'text')->setAttributes('email', array('label' => 'Email', 'size' => 32));
$renderer->setType('language', 'select')->setAttributes('language', array('label' => 'Language'));
$renderer->setType('accept', 'checkbox')->setAttributes('accept', array('label' => 'Accept the terms?'));
$renderer->setType('submit', 'submit');

// organize form fields into sections
$renderer->addSection('Enter personal data', array('username', 'password', 'email'));
$renderer->addSection('Enter settings', array('language', 'accept'));

// render form to html
$html = $renderer->render();

// pass rendered form to the view
$view = new Zend_View();
$view->form = $html;
$view->render('user_form.tpl');
{code}
{card}

{card:label=UC 04 : Array rendering}
h3. UC 04: Use an array with all form values and fields to be used by Smarty

{code}
// create and validate form
$form = new User_Form('my_user_form');
$form->populate($_POST);

if ($form->validate()) {
$values = $form->export();

// process data, e.g. save to database
}

// pass form array to the view
$smarty = new Smarty();
$smarty->assign('form', $form->toArray);
$smarty->assign('languages', array('en' => 'English', 'oz' => 'Strine', 'nz' => 'Kiwi'));
$smarty->display('user_form.tpl');
{code}

Usage in the smarty template:

{code}
<h1>Enter user details</h1>
<form id="{$form._head.id}" method="post" action="{$form._head.actionPath}">
<fieldset>
<legend>Enter personal data</legend>
<p><label for="username">Username</label>
<input type="text" {$form.username.attributes} size="32" /></p>
<p><label for="password">Password</label>
<input type="password" {$form.password.attributes} size="32" /></p>
<p><label for="email">Email</label>
<input type="text" {$form.email.attributes} size="32" /></p>
</fieldset>
<fieldset>
<legend>Enter settings</legend>
<p><label for="languages">Language</label>
<select name={$form.language.name} size="1">
{foreach from=$languages item=option key=value}
<option {$form.language.options.$value.selected} value="{$value}">{$option}</option>
{/foreach}
</select></p>
<p><label for="accept">Accept the terms?</label>
<input type="checkbox" {$form.accept.attributes} value="{$form.accept.value}" {$form.accept.checked} />accept terms</p>
</fieldset>
<p><input type="submit" value="Enter" /></p>
</form>
{code}
{card}

{card:label=UC 05 : Form model}
h3. UC 05: Define the form model

{code}
// define form model by extending the Zend_Form abstract class
class User_Form extends Zend_Form
{
protected $_defaults = array('lang' => 'oz', 'accept' => 'on')

protected function _setup()
{
// add field and validation rules step by step
$field = $this->add('username');
$field->addRule(new Zend_Rule_String(), 'A custom error message');
$field->addRule(new Zend_Rule_Range(5, 32));
$field->addRule(new Zend_Rule_Regex('/^\w+$/'));

// add field and validation rule in one step and more rules in subsequent steps
$field = $this->add('password', new Zend_Rule_String());
$field->addRule(new Zend_Rule_Range(5, 16));
$field->addRule(new Zend_Rule_Regex('/^\w+$/'));

// alternatively add field and email rule using fluent interface
$this->add('email')->addRule(new Zend_Rule_Email());

// add language selection, note that only the possible keys for the options are passed
// the form model does not care about the option values
$this->add('language')->addRule(new Zend_Rule_Enum(array('en', 'oz', 'nz')));

// add accept field
$this->add('accept')->addRule(new Zend_Rule_Set(array('on')));

// add a rule for a couple of form fields
$this->addRule(new Zend_Rule_Required(), array('username',
'password',
'lang',
'accept'));

// add a filter for all form fields
$this->addFilter(new Zend_Filter_Notags());
}
}
{code}
{card}

{card:label=UC 06 : Validating}
h3. UC 06: Use the form model to validate and render a form using Zend_View

{code}
$form = new User_Form('my_user_form');
$form->populate($_POST);

if ($form->validate()) {
$values = $form->export();

// process data, e.g. save to database
}

$form->action = 'user/process/';
$form->method = 'post'; // would be post by default in Xhtml renderer, but this illustrates usage

$view = new Zend_View();
$view->form = $form;
$view->render('user_form.tpl');
{code}
{card}

{card:label=UC 07 : Error handling}
h3. UC 07: Handling error messages

For this use case we assume that the validation already took place and the following validation errors occured:

* username was too short
* email was invalid
* the accept checkbox was not ticked

{code}
$form = new User_Form('my_user_form');
$form->populate($_POST);

if ($form->validate()) {
$values = $form->export();

// process data, e.g. save to database
}

$form->setActionPath('user/process/');

// build an array with the possible error messages
$messages = array('username' => array('required' => 'Please enter user name',
'range' => 'User name must contain between 5 and 32 chars',
'regex' => 'Only characters, digits and underscore allowed'),
'password' => array('required' => 'Please enter a password',
'range' => 'Password must contain between 5 and 32 chars',
'regex' => 'Only characters, digits and underscore allowed'),
'email' => array('email' => 'Your email adress is invalid'),
'language' => array('required' => 'Please select a language'),
'accept' => array('required' => 'Please accept site terms'));

$view = new Zend_View();
$view->form = $form;
$view->messages = $messages;
$view->errors = $form->getErrors();
$view->render('user_form.tpl');
{code}

Now $view->errors will hold the following array:

array('username' => array('range'), 'email' => array('email'), 'accept' => array('required'));

Template view:

{code}
<h1>Enter user details</h1>

<?php if (count($this->errors) > 0) : ?>
<p>Some errors occured please check your input:</p>
<?php endif; ?>

<form id="<?php echo $this->form->getId() ?>" method="post" action="<?php echo $this->form->getActionPath() ?>">
<fieldset>
<legend>Enter personal data</legend>
<p><label for="username">Username</label>
<?php echo $this->formElement('text', $this->form->username, array('size' => 32)) ?></p>
<?php if ($this->errors['username'])) {
foreach ($this->errors['username'] as $error)
{
echo '<p>' . $this->messages['username'][$error] . '</p>';
}
} ?>
<p><label for="password">Password</label>
<?php echo $this->formElement('password', $this->form->password, array('size' => 32)) ?></p>
<?php if ($this->errors['password'])) {
foreach ($this->errors['password'] as $error)
{
echo '<p>' . $this->messages['password'][$error] . '</p>';
}
} ?>
<p><label for="email">Email</label>
<?php echo $this->formElement('text', $this->form->email, array('size' => 32)) ?></p>
<?php if ($this->errors['email'])) {
foreach ($this->errors['email'] as $error)
{
echo '<p>' . $this->messages['email'][$error] . '</p>';
}
} ?>
</fieldset>
<fieldset>
<legend>Enter settings</legend>
<p><label for="languages">Language</label>
<?php echo $this->formElement('select', $this->form->language, null, array('en' => 'English', 'oz' => 'Strine', 'nz' => 'Kiwi') ?></p>
<?php if ($this->errors['languages'])) {
foreach ($this->errors['languages'] as $error)
{
echo '<p>' . $this->messages['languages'][$error] . '</p>';
}
} ?>
<p><label for="accept">Accept the terms?</label>
<?php echo $this->formElement('checkbox', $this->form->accept, null, array('on' => 'accept terms')) ?></p>
<?php if ($this->errors['accept'])) {
foreach ($this->errors['accept'] as $error)
{
echo '<p>' . $this->messages['accept'][$error] . '</p>';
}
} ?>
</fieldset>
<p><?php echo $this->formElement('submit', 'submit', array('value' => 'Enter') ?></p>
</form>
{code}
{card}

{card:label=UC 08 : Form generation}
h3. UC 08: Automatic form generation

{code}
// create an instance of a model class which extends Zend_Db_Table
$model = new User_Model();

// create an instance of the form generator based on the model
$generator = new Zend_Form_Generator_Db(model);

// tries to read the cache first and if this failed, generates the form model
$form->accept($generator);

// set the form name and continue to work with the form
$form->id = 'my_user_form';
$form->populate($_POST);

if ($form->validate()) {
$values = $form->export();

// process data, e.g. save to database
}
{code}
{card}

{card:label=UC 09 : Form processor}
h3. Use Case #9: Form processor

*Note*: Darby, please specify a use case here for for form processor model.
{card}
{deck}
{zone-data}

{zone-data:skeletons}
{composition-setup}
{deck:id=Source}
{card:label=Zend_Form}
{code}
abstract class Zend_Form implements Countable, Iteratable
{
protected $_attr = array();
protected $_submit = array();
protected $_default = array();
protected $_validator = array();
protected $_filter = array();
protected $_fields = array();

public function __construct($name = false) {}
public function __get($field) {}
public function __set($field, $value) {}
public function __toString() {}
public function populate($submit) {}
public function export() {}
public function setDefaults($defaults) {}
public function getDefaults() {}
public function validate() {}
protected function _validate() {}
abstract protected function _setup();
public function add(Zend_Form_Field_Interface $field) {}
public function remove($field) {}
public function addRule($rule) {} // All or selected fields
public function removeRule($rule)
public function addFilter($filter) {} // All or selected fields
public function removeFilter($rule)
public function import() {} // Accepts 'plugin' form generators
public function toArray() {}
public function setRenderer(Zend_Form_Renderer_Abstract $renderer) {}
}

class Zend_Form_Exception extends Zend_Exception {}
{code}
{card}

{card:label=Zend_Form_Field}
{code}
class Zend_Form_Field
{
protected $_value;
protected $_attr = array();
protected $_validator = array();
protected $_filter = array();
protected $_error = array();

public function __construct($name, $attr = null) {}
public function __get($field) {}
public function __set($field, $value) {}
public function __toString() {}
public function setValue($value) {}
public function getValue() {}
public function isValid() {}
public function addRule($rule) {}
public function removeRule($rule) {}
public function addFilter($filter) {}
public function removeFilter($filter) {}
public function toArray() {}
protected function _setError($error) {}
}
{code}
{card}

{card:label=Extended Zend_Form_Fields}
{code}
class Zend_Form_Field_Enum extends Zend_Form_Field
{
public function __construct($name, $options = false) {}
}

class Zend_Form_Field_File extends Zend_Form_Field
{
public function __construct($name, $type = false) {}
}

class Zend_Form_Field_Set extends Zend_Form_Field
{
public function __construct($name, $options = false) {}
}

class Zend_Form_Field_Text extends Zend_Form_Field
{
public function __construct($name, $range = false) {}
}
{code}
{card}

{card:label=Zend_Form_Html}
{code}
abstract class Zend_Form_Html extends Zend_Form
{
}
{code}
{card}

{card:label=Zend_Form_Renderer_*}
{code}
abstract class Zend_Form_Renderer_Abstract
{
public function _construct($form) {}
abstract public function render();
public function setType($field, $type) {}
public function setAttributes($field, $attributes) {}
public function addSection($field, $fields) {}
}

class Zend_Form_Renderer_Html extends Zend_Form_Renderer_Abstract
{
public function render() {}
}
{code}
{card}

{card:label=Zend_Form_Generator_Abstract}
{code}
abstract class Zend_Form_Generator_Abstract
{
public function _construct($form) {}
abstract public function generate();
}
{code}
{card}
{deck}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>