Zend_Form was a big step forward with the 1.5 release; it provided a number of things that simply worked out of the box:
- Filtering against XSS attacks
- Form output
That said, it came with several problems:
- Since validation and filtering were integrated into the form, there was no way to push that to the domain model easily; many developers found they were needing to duplicate validation and filtering efforts.
- This is largely due to the fact that validation/filter chains are attached to individual elements, and not the form itself, making aggregation of all rules difficult.
- The decorators, while powerful and flexible, (a) are also difficult to explain to new users, and (b) can pose new difficulties to the end-user (what precedence is taken? how do I alter output of this decorator? why does this one allow wrapping in HTML tags, and this one does not? etc.)
The chief goals of this RFC for refactoring Zend\Form are:
- 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, fieldsets, and elements would primarily be value objects. Forms would contain fieldsets and elements, and metadata describing the form. Elements would contain their value, and then metadata describing the element.
Forms will aggregate:
- Form metadata (action, method, id, etc.)
- 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.
It's unusual to validate an element in isolation. As such, validation chains should be attached only to the form.
- MUST allow detaching/attaching validation/normalization chains, hereafter titled "input filters"
- MUST allow defining a tree of elements (i.e., (nested) fieldsets of elements)
- MUST allow retrieving a tree of error messages
- MUST allow passing element-specific error messages to that element
- MUST allow partial validations
- SHOULD allow indicating which specific elements must be valid
- COULD allow form elements to hint which validators/filters are desired, e.g., via annotations; a directive would then tell the form to use this data when retrieving the input filter.
In practice, the input filters would operate similarly to Zend\Filter\Input, though the goal is to provide a more programmatic API as well as a factory for generating the chains. Additionally, the chains for individual elements should be detachable.
The desired outcome is something like this:
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.
If you're not binding validated values to an object, you can simply retrieve them as an array.
To accomplish partial validation, you hint to the form which elements you're interested in prior to validation.
Decorators are an elegant way to render elements. However, they are also very difficult to teach, particularly to developers not familiar with design patterns. (It also doesn't help that the implementation is mis-named; they more accurately follow the Visitor pattern.)
An additional problem in ZF1 is that developers and/or designers often want very fine-grained control of the output, making the decorators a poor fit.
My proposal is to instead create new and/or modify existing form view helpers. These would consume the form and/or element metadata in order to create output; a given object could be passed to several helpers to create compound output when desired. As a simple example:
A view helper per general form input type will be provided, along with "generic" view helpers that will introspect the type and proxy to the appropriate specific view helper.
"Wait, this looks like a lot of work!" I hear some saying. However, using the lesson from decorators, repitition can be easily addressed: create helpers for common combinations:
and the above would be used like this within a view script:
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.
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.
The following classes are used examples below:
These are some of the interfaces for the various objects discussed in the proposal. They should be considered incomplete.