- Speaker: Bernhard Schussek
- Date: Friday 28 September 2012
- Duration: 40m
- Location: Online video (held at the Symfony Live San Francisco 2012 conference)
Slides are available here.
Form trees
In Symfony, a form is a Form
object, and every field of this form is also a Form
. A date field is made of three fields, each of them is also Form
object.
To access any Form
element inside a Form
tree:
<?php
$form->get('element')->get('otherElement');
To create a field:
<?php
$builder->add('name', 'type');
To fill a form tree programmatically, associate a form with a corresponding entity:
<?php
$form->setData($entity);
To fill a form tree via submitted values1:
<?php
$form->bind($request);
Data formats
Requests only transport data as strings.
Each Form object store its data in 2 formats:
- the model data, used in the application
- the view data, used in the views, usually strings
Examples:
- text type: stores both model and view data as strings
- number type: model format is a float; view format is a localized string (for separators, ...)
- date type: model format can be stored as a string, an integer (Unix timestamp), an array or a
DateTime
; view format can be a string or an array depending on the model format (the model format is selected in theinput
option of the field)
Consequence of this modularity: it is really difficult to access the data because each format type has to be handled.
Solution: Each Form
object stores a third normalized format, not configurable, accessible with $form->getNormData()
(for the date type, it is an instance of DateTime
).
The normalized data should contain as much information as possible.
Data transformation
The transformation from model data to normalized data (resp. from normalized data to view data) is done using model transformers (resp. view transformers).
setData()
sets the model data, bind()
sets the view data. Transformations to the other formats are then done using the data transformers in order to keep all data synchronized.
Data transformers implement 2 methods of DataTransformerInterface
: transform($data)
and reverseTransform($data)
.
Data transformers should never change the information stored in a form, but only the data's representation. To change information, use events instead.
To create an extension of an existing class, data transformers can be chained to convert a type to another and then to another type again.
Data mapping
Data mapping is the exchange of information between a form and its fields (its direct children).
When setData()
is called on a form, it also calls setData()
on each field of its direct children. Same behavior appears when calling bind()
on a form. The default and only data mapper of Symfony, PropertyPathMapper
(implementing DataMapperInterface
), calls the getters/setters of the entity.
In order to not store a field value in the model object, i.e. not apply the data mapper to this field, the mapped
option has to be set to false:
<?php
$builder->add('name', 'type', array (
'mapped' => false
));
Since it is not synchronized with the model, data has to be set either using the data
option of the add()
method or the setData()
on this field. To access the value of the field, getData()
must be explicitely called on this field because the model does not store this value.
Customized getters/setters or public property names can be used by setting the property_path
option of the add()
method to the name of these getters/setters or this property. This can also be done with classes implementing the ArrayAccess
interface. The same property can also be used to chain getters/setters. Finally, all these capabilities can be mixed.
Events
Events are meant to modify the content of the form itself: filtering (convert to lowercase, strip HTML tags, ...), cleaning, dynamically modifying forms, ...
A common event is BIND
, called during the data normalization.
Form types vs form instances
Form types exist once in an application and should only have global dependencies that every form needs (e.g. an EntityManager
, ...). Also, they do not change after construction time.
Form instances exist multiple times and can and will change after construction.
Do not pass data to the constructor of form types.
Use the PRE_SET_DATA
event instead, called at the beginning of the setData()
method. Allows you to attach children to the form before processing the method.
Q&A comments
- Dynamic form fields should be added and removed using events instead of creating all of them in the type and then remove them when not needed.
- Advice on performance when creating big forms: rely on Symfony optimizations to display multiple entity fields instead of doing custom DB queries, and profile.
- To display an embedded form along with another form, use form themes.
-
As of Symfony 2.3, this is done using
$form->handleRequest($request);
↩