Riskle_Form, a quick wrapup
So well, I've been busy these days working on my own implementation of a form component in the ZF spirit. This post is to help me see where I'm at with this component, as well as planning future evolution. I'll try my best to describe what it does and does not, and what it could do in the future.
Of course, while this is more of a personnal pense-bete than anything else, any comments are welcome.
Basic features
Forms are build using the factory, which ensures each form has a unique name so that two forms can't conflict when used on the same action. Each element of the form is materialized by an instance of Riskle_Form_Element which holds any configuration directive for the element.
Prototyping
The component features an extensible prototyping system via the Riskle_Form_Prototype set of class. Default (and mandatory atm) is to use a Zend_Config_Ini file which can in turn contains further prototyping instructions, such as prototype from a model (by model I mean a class extending Zend_Db_Table). A given form can embed multiple prototypes, possibly of the same type, which have to be conretized before the form is usable. Concretization of form operates in a FILO merging fashion. Prototyping from a model is for the moment very basic, it treats all fields as text field, but this will be addressed in time. Of course, the system allows for different source of prototyping (think Riskle_Form_Prototype_Xml for example).
Form elements
Elements features a variety of configuration directives, which I'll details here.
- type: explicitly tells the type of the field (for the moment, better tell an existing HTML type). default is
text. - label: basically, the string to be put into the <label /> tag.
- validators: an array of validators, see Data validation for details.
- allow_empty: weither the field is allowed to be empty or not.
- datasource: specify a datasource to retrieve the field datas from (only used for <select /> fields). See below for further details about that.
- depends: specify a field to which the current field is dependent. Mainly used for auto-ajax-refreshing <select />.
The datasource directive deserves a little more explanation. When the form factory encounters a datasource directive, it delegates the work to the datasource factory, which autodetects and decode the datasource. The core distributions handles the following datasources:
- Instanciated Model, with format:
YourModel->method - Static Model, with format:
YourModel::method - CSV string, with format:
csv:foo,bar. It uses a user land CSV decoder that will most likely be bundled with the core distribution when (if ?) released. - JSON string, with format:
json:{'foo', 'bar'}. It's worth noting that this example will be converted to the following associating array:array('foo' => 'foo', 'bar' => 'bar'). You can avoid this by including a noassoc switch (so that it readsjson:noassoc:{'foo', 'bar'}. Another note on JSON: the main problem when allowing JSON in INI files is that you can't have a double quote character in values (except by using a dirty hack which I do not want), and the php json decoder expects identifiers to be doublequoted. The datasource handles this by replacing any non escaped single quote to a double quote.
Data validation
Data validation is done by a subclassed Zend_Input_Filter. validators configuration from elements should conform to ZFI syntax. The custom Riskle_Form_Input component features a getRaw set of methods to retrieve raw data (used in case the form was not successful). Not much else to say here.
Rendering
The core distribution features a set of view helper aimed at easing html form drawing. The helpers use the built-in ZF view helpers to render fields while adding a bunch of custom markup around (namely: labels and error messages). The default helpers also take care of fields that depend on other fields (see Form elements) and add the according ajax queries to populate the element.
Workflow control
The most interesting part I think. Riskle_Form features a plugin system, much like the Zend_Controller one. It exposes the following hooks (in this order):
formStartup, always run at the beginning of the workflowprePopulate, run before the form gets it data (either from the model or the request)postPopulate, run after the form gets it datapreValidate, run before the forms attempts to validate the dataonValidateSuccess, run if and only if the data validation was a successonValidateFailure, run if and only if the data validation was a failurepostValidate, run after the forms attempts to validate the data (regardless of the success or not)preCommit, run before the form tries to feed the model with the dataonCommitSuccess, run if and only if the data feedage was a successonCommitFailure, run if and only if the data feedage was a failurepostCommit, run after the forms tries to feed the model with the data (regardless of the success or not)formShutdown, always run at the end of the workflow
For each hook, the instance of the current form is passed as an argument to the method. Of course, the commit set of hooks will only be run if the data validates.
Sample usage
A simple uploading form could look like the following:
[php]
$form = Riskle_Form::factory(new Zend_Config_Ini('upload.ini', null));
$form->registerPlugin(new Riskle_Form_Plugin_Upload('myUploadField'));
$form->run();
$this->view->assign('form', $form);
the view would look like:
echo $this->drawForm($this->form);
and upload.ini could look like:
[ini] [form] model = "uploaded_files" prototype = 1 [fields] myUploadField.type = "file" myUploadField.label = "choose an image" legend.type = "text" legend.label = "Legend"
What it lacks
Clearly, this form component is designed for use in an HTML component. Few or no care has been taken to ensure cross-medium compatibiliy such as XHTML, PDF, etc. Hopefully the changes to apply to achieve such compatibility will not be that much of a hassle. While I'm not willing to ensure such a compatibility, I'd be happy to apply any patch submitted.
Regarding plugins, they still miss the ability to really act on the workflow, such as stopping it, skipping steps (?) or add errors to the stack, etc.
Also, the code still needs a few refactoring and optimization, especially regarding the view helpers and the main form class, as well as the configuration file syntax which needs an in-depth revision.
Comments
Are you going to take part in the Zend_Form competition ? --> http://framework.zend.com/wiki/page...
;-)
hell noes ! :-)
but you may (surely) find some of my concepts in jurrien proposal soon as we often discuss forms on irc (#zftalk@chat.freenode.net if anyone willing to join)
Mirmo sure has some great ideas that will end up in my proposal. The latest of which is the plugin architecture (although a different implementation).
Thanks mirmo!