compared with
Key
This line was removed.
This word was removed. This word was added.
This line was added.

Changes (33)

View Page History
* Separate validation/normalization from the form object hierarchy; forms should consume validation/normalization chains only, and only for purposes of error reporting.
* Move view-related functionality -- aka, the decorators -- to the view layer, and make the functionality more declarative/programmatic.
* Forms, subforms, fieldsets, and elements would primarily be value objects. Forms would contain subforms fieldsets and elements, and metadata describing the form. Elements would contain their value, and then metadata describing the element.

h3. General Architecture

* Form metadata (action, method, id, etc.)
* Elements and other forms (i.e., subforms)
* Elements and fieldsets (i.e., groups of elements)
* Validation/Normalization chains (input filters)

Additionally, a Factory subcomponent would be provided. The factory will create Forms (and all the objects forms composes). Optionally, it can use a Builder, which will try to automate creation of forms based on annotated models and a provided Form Definition object.

h3. Validation/Normalization


* *MUST* allow detaching/attaching validation/normalization chains, hereafter titled *"input filters"*
* *MUST* allow defining a tree of elements (i.e., (nested) subforms fieldsets of elements)
* *MUST* allow retrieving a tree of error messages
** *MUST* allow passing element-specific error messages to that element
{code:php}
$entity = new SomeEntity($data);
if (!$entity->isValid()) {
$form = new SomeForm();
$form->setInputFilter($entity->getInputFilter());
{code}

Alternately, you could bind a model (an object). The form would validate data via its composed input filter, but, on validation, pass the normalized values into the model.

{code:php}
$entity = new SomeEntity();
$form->bindInput($data);
$form->bindModel($entity);
if ($form->isValid()) {
// do something with the entity
$repository->save($entity);
} else {
// redisplay form
}
{code}

If you're not binding validated values to an object, you can simply retrieve them as an array.

{code:php}
$entity = new SomeEntity();
$form->bindInput($data);
if ($form->isValid()) {
// Grab the values. If not bound to a model, returns an array; otherwise,
// this returns the model object
$values = $form->getData();
} else {
// redisplay form
}
{code}


To accomplish partial validation, you hint to the form which elements you're interested in prior to validation.

{code:php}
$form->setValidationGroup('username', 'password', 'password_confirmation');
if ($form->isValid()) {
$values = $form->getData(); // contains only 'username', 'password' and 'password_confirmation'
}
// setValidationGroup(Form::VALIDATE_ALL) resets to default behavior
{code}

h3. Rendering

The goal for ZF2 is to provide a basic set of these "combination" helpers that can be used to accomplish basic form markup with minimal effort by developers. For anything more complex or specific, developers will need to write their own helpers and/or turn to third party modules.

h3. Object binding and form builder component
h3. Factory, Builder, and model binding

{color:#000000}Provide a service\builder\definition to generate forms from objects automatically and bind form data directly to the object. This is great for RAD and with the refactor/bc break of Zend_Form it would be a shame not to include out of the box.{color}
* *MUST* allow binding form data directly to objects.
* *MUST* include a form builder service to build annotated objects automatically (type guessing, filters, validators, etc)
* *MUST* be extensible to allow module developers to developer additional annotations, e.g., Doctrine ORM\ODM.
* *SHOULD* provide caching to reduce the performance hit of building forms from annotations/reflection.S
The Factory will be used to create form objects, filtersets, elements, and the input filter used by the form based on the configuration provided.

The Builder component would provide a way to generate forms from models automatically. Additionally, Form would provide methods for mapping values to models automatically. These features will benefit rapid application development.

* *MUST* provide a factory for form elements
* *MUST* provide a factory for filtersets
* *MUST* provide a factory for forms
* *MUST* provide a factory for the input filter
* *MUST* allow binding form data directly to objects (models).
* *SHOULD* include a form builder service to build forms from annotated objects automatically (type guessing, filters, validators, etc)
** *SHOULD* be extensible to allow module developers to developer additional annotations, e.g., Doctrine ORM\ODM.
** *SHOULD* provide caching to reduce the performance hit of building forms from annotations/reflection.

h4. Sample class and definition

class User
{
public function build(Builder $builder)
{
// Builder::add($name, $type, $options);
// options would be merged (override) annotation supplied options; if no
// annotations are found, or no model_class specified, these would be
// add($name, $type, $options); // options would be merged (override) annotation supplied options used alone.

$builder->add('username') // An exception is thrown because no element annotation or $type was provided.
->add('password'); // Element is type "password" as specified by annotation.
}

// Used to uniquely identify the form.
// Typically, this is the id and/or name used when rendering the form.
public function getName()
{
}

// Sets default Zend\Form options (action, method, etc) as well as specifying the data
// specifying the model class for the definition (or anything else required).
// required).
public function getOptions()
{
return array('data_class' array('model_class' => 'My\Model\User');
}
}
// username: foo
// password: bar
//
// Assume a configured form factory with a configured form builder has been
// injected into the controller.
public function createAction()
{
$request = $this->getRequest();
$manager = $this->getFormManager():
$userForm = $manager->getForm('user');
$form = $this->formFactory->getForm('My\Form\Definition\User');

if ($request->isPost()) {
$userForm->bindInput($request->post());

$user = $userForm->getData(); // My\Model\User
echo $user->username; // foo
echo $user->password; // bar
$user->password = 'bar2';

$request = $this->getRequest();
$form = $this->formFactory->getForm('My\Form\Definition\User');
$manager = $this->getFormManager(): $form->bindModel($user);

// Second argument populates username and password with foo2 and bar2, respectively
$userForm = $manager->getForm('user', $user);

return new ViewModel(array('form' => $userForm));
}
{code}

h3. Interfaces

These are some of the interfaces for the various objects discussed in the proposal. They should be considered incomplete.

{code:php}
interface FormInterface extends IteratorAggregate, FieldsetInterface
{
public function bindInput($data);
public function bindModel(object $model);
public function isValid();
public function getData();
public function setInputFilter();
public function getInputFilter();
public function setValidationGroup(); // likely proxies to input filter
}
{code}

{code:php}
interface FieldsetInterface extends ElementInterface
{
public function add($elementOrFieldset, $order = null);
public function remove($elementOrFieldset);
public function getElements();
public function getFieldsets();
public function getIterator(); // iterate over elements and filtersets in order
public function setMessages($messages); // hash of element names => messages
public function getMessages($elementName);
}
{code}

{code:php}
interface ElementInterface
{
public function setMetadata($keyOrTraversable, $value = null);
public function getMetadata($optionalKey = null);
}
{code}

{code:php}
interface DefinitionInterface
{
public function getName();
public function getOptions();
public function build(Builder $builder);
}
{code}

{code:php}
// allows listeners so that a listener can intercept types, inject options, etc
interface BuilderInterface extends \Zend\EventManager\EventManagerAware
{
// Map of element types => classes
public function setTypeMap($typeMap);

// Accept definition object or class name
public function getForm($definition);

// Annotations we know about
public function setAnnotations($annotations);

// Add an element to the form being built
public function add($name, $type = null, array $options = array());
}
{code}