FormFactory – Driving Doctrine 1.2 / 2.x Mappings into Zend_Form objects
On a few of my previous projects I found myself creating more form classes than I’d like. And after the 30th one I figured there had to be a better way. I quickly realised that most of the elements within these forms shared similarities to the data type I would use on my database definitions. As I was using Doctrine at the time I figured I could not only drive my database from my mapping definitions, but my forms too.
So the idea would be to take all of the possible data types allowed in a doctrine schematic and provide a default form element for them. So for example a string column could become a text field, or an enum could be a checkbox. Now obviously this would need to be configurable and be easily overridden with another element type when required.
The plan was never to solve all my form problems, but to reduce the workload on creating the simple ones. However I found that simply providing an override configuration wasn’t enough, and that I needed slightly more control over my elements. So I did two things;
- created a bunch of quick configuration options such as a default value, whether its a required element or the ability to disable it all together. And,
-Allowed a fall back onto Zend_Form’s ini configuration, giving me a greater flexibility when required.
I’m using this library in my current project but have only driven the forms using the doctrine 2.x adapter. There is some work to be done to have it drive off of doctrine 1.x mappings, and i’d also like to be able to parse a sql create file with it at some point too. The code is on github so feel free to fork and have a play. I would love to hear any feedback on any changes / issues / improvements.
https://github.com/leedavis81/FormFactory
Main Features;
Use doctrine mappings to create fast flexible zend forms
FormFactory will enable to drive your forms from your Entity annotations. It uses a default configuration for each attribute of your Entity and builds a Zend Form Element from it. For example a column of type ‘string’ will by default become a text input. These are completely configurable, columns can be ignored or customised to quickly build the form you need. Zend Form objects are returned for further customisations if required.
Example, generating a form from my User entity
$form = new FormFactory\Form('User', 'add');
echo $form->getZendForm();
or, you can use the quick static call, taking advantage of PHP 5.3′s __callStatic method
echo FormFactory\Form::AdminUser('edit');
inherit / override config definitions from other forms
Any definitions provided for a form can be extended for other uses. for example if using ini configurations
[add] ;...some configurations... [edit : add] ; removes the password element when editing config.password.disabled = true;
Add a relationship of entities to a single form
You may want to use several Entities to generate your form. By passing a relationship to the form constructor additional attributes of those Entities will be added to your form. eg
$form = new FormFactory\Form(array('User' => array('UserPassword', 'UserAddress')), 'add');
echo $form->getZendForm();
Allows you to fall back to a native Zend_Form configuration
If the default configuration options aren’t enough, you have the power of customising your form elements using typical zend_form configurations. This is very useful for adding validators / filters / decorators to your elements.
form.elements.username.type = text form.elements.username.options.label = Username form.elements.username.options.order = 0 form.elements.username.options.attribs.class = 'small usernameCheck' form.elements.username.options.required = 1 form.elements.username.options.description = 'Must be greater than 3 characters' form.elements.username.options.validators.user-exists.validator = "UserExists"
Creating elements using a native Zend_Form configuration will override any default FormFactory element with the same name.
Linking data to multi type element
Multi type form elements such as select boxes, radio buttons or check boxes require populating with a set of name / values. There are currently three ways to do this;
Add link data via a config file
You can set multiple field values directly via your form configuration by passing an array of names and values. Note the length of these arrays must be identical.
config.role_id.linkData.names = Administrator, Operator config.role_id.linkData.values = 1, 2
Add link data by selecting from another entity
You may have a set of Data that you want to appear in a select element. Using the FormFactory adapters this data can be pulled in and injected into you form. For example, I want my Users Entity column ‘role_id’ to be a select field populated with the data currently persisted for that Entity.
config.role_id.linkEntity.entityName = Role config.role_id.linkEntity.entityField = id config.role_id.linkEntity.entityFieldValue = name
You can even filter on where status is equal to 1
config.role_id.linkEntity.filters.status = 1
Add Link data via a plugin hook
If you need to perform slightly more work to retrieve the multi field names and values you can use a plugin to do so. This class must extend the \FormFactory\Plugin\LinkData class and implement the getOptions() method like so;
class System_FormFactoryPlugin_ServiceHolidayBoard
extends \FormFactory\Plugin\LinkData
{
public function getOptions()
{
return \Entities\Repositories\ServiceHolidayRepository::$boardNames;
}
}
and set in your config like;
config.{elementName}.linkDataPlugin = My_Plugin_Class
Has its own autoloader and bootstrapping mechanism
Library can be quickly bootstrapped by including a ClassLoader, and passing a path to the formfactory config file. for example..
require_once realpath(APPLICATION_PATH . '/../library/FormFactory/ClassLoader.php'); $formFactory = new FormFactory\Bootstrap(realpath(APPLICATION_PATH . '/configs/form/config.ini'));
Configure a caching mechanism for your config files
Its likely your configuration files are not going to change in a production environment, so you can pass in a caching mechanism to stop the library from having to re-read them every time a form is produced. This can be done like so;
ff.cache.frontend.name = Core; ff.cache.frontend.options.lifetime = 30; ff.cache.frontend.options.automatic_serialization = true; ff.cache.backend.name = Apc;
Quick config options
Here are some of the common configurations I found myself needing when setting up simple forms.
config.{entityname}.value = {default value}
config.{entityname}.label = {label name}
config.{entityname}.required = true
config.{entityname}.disabled = {true}
config.disabled = {column1, column2, column3}
Its available now on github, have fun with it.
https://github.com/leedavis81/FormFactory
Requirements:
- PHP 5.3
- Doctrine 1.2 / 2
- Zend – Config, Form

I’ve used this package in a couple of projects. I have to say, it rocks! Thanks Lee :razz:
Could you guys please provide a living example?
It drives me nut’s not getting this working.
BTW, I fixed some typos and after I finally got the entity manager passed to FF, I stuck on this:
ViewHelper decorator cannot render without a registered view object
I’m using not ZF, but FatFreeFramework and Doctrine2. How to use FF without ZF MVC?
Many thanks in advance,
Sven
Yeah!
I’m just a step further:
I have this in my controller:
FormFactory\Bootstrap::setConfig($this->get('APP')."/Configs/FormFactory/config.ini");
FormFactory\Config\General::setEntityManager($this->em);
$v = new Zend_View();
$zf = FormFactory\Form::Customer('add');
$this->set('form', $zf->render($v));
As you can see, I rewrote General::setEntityManager() to static.
I found no other way to pass em to the static call of the FF-Form.
Now I got the form output in my fatfreeframework view and fiddle around with configuring the form.
Bye Sven
Hi Sven, for bootstrapping you need to do a small number of things;
* require the FF classloader
* pass in your FF configuration file
* if using Doctrine2, pass in your entity manager.
The bootstrap I’m currently using is;
require_once realpath(APPLICATION_PATH . '/../library/FormFactory/ClassLoader.php');
$formFactory = new FormFactory\Bootstrap(realpath(APPLICATION_PATH . '/configs/form/config.ini'));
\FormFactory\Config\General::getInstance()->setManager($EntityManager);
Also, this library extends from the Zend Form (1.x) component, so will need to have access to those classes.
Hi there,
Bootstrapping is no problem anymore I just took another way.
But I don’t know how to configure, adjust my forms. Where do I pass the options you mentioned in example ini?
A little example source project would be very helpful and self explaining.
Thanks for your qick respnse,
bye Sven
Hi Sven.
An example project would definitely be useful. I’ll get one set up as soon as I get time.
In the formfactory configuration file you should define a path to your model specific configurations:
ff.config.path = ‘path/to/my/fom/configs’;
And then each form config should be named according to the model / entity. So if you have a User entity, you can go ahead and create a user.ini file, with whatever settings you require.
Then by calling:
$form = new FormFactory\Form(‘User’, ‘add’);
You’ll have your form object.
The “add” reflects the section in your configuration. This can be used to extend other configurations using the same entity.
Hope that makes sense.
Hi,
the things work now. I misconfigured the path option in the config. I accidentially put a $path variable in the path value and no Zend_Config style constant like APPLICATION_PATH.
I needed to study your code a while to discover the mistake.
So now I injected a submit button into my entity form through the form.elements configuration option. But it appears at the beginning of the form, not at the end. How to specify the order of elements and what is your way to “translate” the labels?
Thanks a lot for helping,
bye Sven
There are a number of “quick” configuration options available to you via the formfactory lib (these can be seen in the example.ini config file).
; config.{entityname}.value = {default value}
; config.{entityname}.label = {label name}
; config.{entityname}.required = true
; config.{entityname}.disabled = {true}
For more advanced settings you can use a standard zend form configuration. As long as the element name matches one of your entity properties, it wont be duplicated. So to put in a submit button you could do..
form.elements.submit.type = ‘image’
form.elements.submit.options.order = 999
form.elements.submit.options.attribs.src = ‘/images/my_submit_button.png’
form.elements.submit.options.alt = “Submit”
Or as an example of what I did recently, creating a textarea that uses ckeditor applies HTMLPurify filtering and appears in 3rd position on my form (uses zero offset):
form.elements.description.type = textarea;
form.elements.description.options.attribs.class = ckeditor-light
form.elements.description.options.filters.html_purify.filter = “HTMLPurify”
form.elements.description.options.order = 2
This describes the process clear and straight forward.
Thank you,
bye Sven
Uhm,
the next issue:
How do you populate Entity Data to the form?
This code in my controller does’nt work:
function viewEditCustomer()
{
$c = $this->em->getRepository('Entities\Customer')->find(1);
$form = new FormFactory\Form('Customer','edit');
$this->set('form', $form->getZendForm()->setDefaults($c->toArray()->render(new Zend_View)));
$this->set('template','forms/customer_add');
}
It produces this error:
htmlspecialchars() expects parameter 1 to be string, array given
#0 /home/sven/webprojekte/eigene/canoe/library/f3-2.0.10/base.php:1685 F3::error(500,’htmlspecialchars() expects parameter 1 to be string, array given’)
#1 {closure}(2,’htmlspecialchars() expects parameter 1 to be string, array given’,'/home/sven/webprojekte/eigene/canoe/library/ZendFramework-1.11.11-minimal/library/Zend/View/Abstract.php’,905,array(‘var’=>array()))
#2 htmlspecialchars(array(),2,’UTF-8′)
Thanks,
Sven
The factory returns a Zend_Form object. From that point onward you should interface with it just as you would with Zend_Form.
// Populating a Zend_Form object
$form->populate($dataArray);
Check out the API / documentation for the Zend_Form component, it should help.