Share
Embed
Symfony2 Form Tricks
Presentation with audio stream on YouTube:
http://www.youtube.com/watch?v=JAX13g5orwo
Held at the Symfony Live San Francisco 2012 conference.
The Symfony2 Form component makes form processing a snap. It features a very flexible architecture and a myriad of configuration options that you can use to implement a wide variety from simple to very complex forms. However, knowing how to use this effectively can be a challenge. This session will give you a deeper understanding of the inner workings of the Form component and show how to leverage this knowledge to solve typical use cases.
Bernhard Schussek
September 28, 2012
Transcript
82 slides
-
Bernhard Schussek @webmozart 2/82 Outline • Introduction • Data formats
• Data mapping • Events -
Bernhard Schussek @webmozart 4/82 Form Form Form Form Form Form
Symfony's Perspective Form Form Form Form Form Form Form Form -
Bernhard Schussek @webmozart 5/82 Form Tree Form Form Form Form
Form Form Form $factory = Forms::createFormFactory(); $form = $factory->createForm(...); -
Bernhard Schussek @webmozart 6/82 Forms Have Names month day year
employee name salary birthDate $builder = $factory->createNamedBuilder('employee'); $builder->add('name'); $builder->add('salary'); $builder->add('birthDate'); $form = $builder->createForm(); -
Bernhard Schussek @webmozart 7/82 Forms Have Names month day year
employee name salary birthDate $form->get('birthDate'); $form->get('birthDate')->get('year'); -
Bernhard Schussek @webmozart 8/82 Forms Have Types month [choice] day
[choice] year [choice] employee [form] name [text] salary [money] birthDate [birthdate] $builder = $factory->createNamedBuilder('employee', 'form'); $builder->add('name', 'text'); $builder->add('salary', 'money'); $builder->add('birthDate', 'birthdate'); $form = $builder->createForm(); -
Bernhard Schussek @webmozart 9/82 Forms Have Data employee [form] data
name [text] data salary [money] data birthDate [birthdate] data day [choice] data year [choice] data month [choice] data -
Bernhard Schussek @webmozart 10/82 Form Lifecycle New Prepopulated Bound setData($data)
bind($data) bind($data) / setData(null) implicitly called -
Bernhard Schussek @webmozart 11/82 Prepopulation employee [form] data name [text]
data salary [money] data birthDate [birthdate] data day [choice] data year [choice] data month [choice] data $employee = new Employee(); $employee->name = 'Jane'; $employee->salary = 110000.0; $employee->birthDate = new DateTime('1941-09-09'); $form->setData($employee); -
Bernhard Schussek @webmozart 12/82 Prepopulation employee [form] Employee Object name
[text] salary [money] birthDate [birthdate] day [choice] month [choice] year [choice] data data data "Jane" 110000.0 DateTime Object data data data 1941 9 9 -
Bernhard Schussek @webmozart 13/82 employee [form] object (Employee) name [text]
"Jane" salary [money] 110000.0 birthDate [birthdate] object (DateTime) day [choice] 1941 month [choice] 9 year [choice] 9 $form->bind($request); -
Bernhard Schussek @webmozart 14/82 ? employee [form] Employee Object name
[text] "Jane" salary [money] 110000.0 birthDate [birthdate] DateTime Object day [choice] 1941 month [choice] 9 year [choice] 9 "1962" "95000.00" "5" "26" "John" -
Bernhard Schussek @webmozart 19/82 Examples [date] string integer array DateTime
string array $builder->add('createdAt', 'date', array( 'input' => 'array', 'widget' => 'single_text', )); -
Bernhard Schussek @webmozart 20/82 class MyDateType extends AbstractType { public
function getParent() { return 'date'; } public function buildForm(FormBuilderInterface $builder) { $builder->addEventListener(FormEvents::BIND, function (FormEvent $event) { // which format do I get??? $data = $event->getForm()->getData(); } ); } } -
Bernhard Schussek @webmozart 21/82 Normalized Format [date] string integer array
DateTime string array DateTime $builder->addEventListener(FormEvents::BIND, function (FormEvent $event) { $data = $event->getForm()->getNormData(); } ); -
Bernhard Schussek @webmozart 22/82 Tip • Whenever you create a
custom type, decide: • Model format? • Normalized format? • View format? http://www.flickr.com/photos/xverges/4622486563/ -
Bernhard Schussek @webmozart 23/82 Data Transformation Form model data view
data normalized data model transformers view transformers setData($data) -
Bernhard Schussek @webmozart 24/82 Data Transformation Form model data view
data normalized data model transformers view transformers bind($data) bind fields and map to view data -
Bernhard Schussek @webmozart 25/82 Data Transformation • Symfony\Component\Form\DataTransformerInterface • bijective
• transform($data) • reverseTransform($data) -
Bernhard Schussek @webmozart 27/82 Example: ChoiceOrTextType [choiceortext] string array string
ValueToChoiceOrTextTransformer -
Bernhard Schussek @webmozart 28/82 Tip http://www.flickr.com/photos/xverges/4622486563/ The normalized data should
contain as much information as possible. -
Bernhard Schussek @webmozart 29/82 Example: ChoiceOrTextType [choiceortext] string array array
ValueToChoiceOrTextTransformer -
Bernhard Schussek @webmozart 30/82 use Symfony\Component\Form\DataTransformerInterface; class ValueToChoiceOrTextTransformer implements DataTransformerInterface
{ public function transform($data) { ... } public function reverseTransform($data) { ... } } Transformer Implementation -
Bernhard Schussek @webmozart 32/82 Case 1: Existing Team Selected [choiceortext]
model data view data normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', )); -
Bernhard Schussek @webmozart 33/82 Case 1: Existing Team Selected [choiceortext]
model data array( "choice" => "Team 1", "text" => "", ) normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', )); -
Bernhard Schussek @webmozart 34/82 Case 1: Existing Team Selected [choiceortext]
model data array( "choice" => "Team 1", "text" => "", ) array( "choice" => "Team 1", "text" => "", ) ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', )); -
Bernhard Schussek @webmozart 35/82 Case 1: Existing Team Selected [choiceortext]
"Team 1" array( "choice" => "Team 1", "text" => "", ) array( "choice" => "Team 1", "text" => "", ) ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', )); -
Bernhard Schussek @webmozart 37/82 Case 2: New Team Created [choiceortext]
model data view data normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Other', 'text' => 'New Team', )); -
Bernhard Schussek @webmozart 38/82 Case 2: New Team Created [choiceortext]
model data array( "choice" => "Other", "text" => "New Team", ) normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Other', 'text' => 'New Team', )); -
Bernhard Schussek @webmozart 39/82 Case 2: New Team Created [choiceortext]
model data array( "choice" => "Other", "text" => "New Team", ) array( "choice" => "Other", "text" => "New Team", ) ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Other', 'text' => 'New Team', )); -
Bernhard Schussek @webmozart 40/82 Case 2: New Team Created [choiceortext]
"New Team" array( "choice" => "Other", "text" => "New Team", ) array( "choice" => "Other", "text" => "New Team", ) ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Other', 'text' => 'New Team', )); -
Bernhard Schussek @webmozart 41/82 Tip http://www.flickr.com/photos/xverges/4622486563/ Data transformers should never
change the information stored in a form, but only the data's representation. To change information, use events. -
Bernhard Schussek @webmozart 42/82 Don't array( 'year' => 2012, 'month'
=> 9, 'day' => 27, ); array( 'year' => 2010, 'month' => 9, 'day' => 27, ); -
Bernhard Schussek @webmozart 43/82 Do array( 'year' => 2012, 'month'
=> 9, 'day' => 27, ); DateTime Object ( [date] => 2012-09-27 07:52:12 [timezone_type] => 3 [timezone] => America/Los_Angeles ) -
Bernhard Schussek @webmozart 45/82 [mydate] array string DateTime [date] string
integer array DateTime string integer array DateTime MyDateTime MyDateTimeToDateTimeTransformer -
Bernhard Schussek @webmozart 46/82 Prepopulation employee [form] data name [text]
data salary [money] data birthDate [birthdate] data day [choice] data year [choice] data month [choice] data $employee = new Employee(); $employee->name = 'Jane'; $employee->salary = 110000.0; $employee->birthDate = new DateTime('1941-09-09'); $form->setData($employee); -
Bernhard Schussek @webmozart 47/82 Prepopulation employee [form] Employee Object name
[text] model data salary [money] model data birthDate [birthdate] model data day [choice] model data month [choice] model data year [choice] model data ? -
Bernhard Schussek @webmozart 48/82 Binding employee [form] Employee Object name
[text] "John" salary [money] 95000.0 birthDate [birthdate] DateTime Object day [choice] 1962 month [choice] 5 year [choice] 18 ? -
Bernhard Schussek @webmozart 50/82 Data Mapping Data Mapping is the
exchange of information between a form and its fields. -
Bernhard Schussek @webmozart 51/82 employee [form] model data name [text]
model data salary [money] model data birthDate [birthdate] model data setData($empl->getSalary()) 110000.0 setData($empl->getBirthDate()) DateTime Object setData($empl) Employee Object setData($empl->getName()) "Jane" -
Bernhard Schussek @webmozart 52/82 employee [form] Employee Object name [text]
salary [money] birthDate [birthdate] model data model data model data $empl->setBirthDate($form->getData()) $empl->setSalary($form->getData()) $empl->setName($form->getData()) bind($request) "John" 95000.0 DateTime Object -
Bernhard Schussek @webmozart 53/82 Data Mapping • Symfony\Component\Form\DataMapperInterface • bijective
• mapFormsToData(array $forms, $data) • mapDataToForms($data, array $forms) -
Bernhard Schussek @webmozart 54/82 Implementations • In Symfony: • PropertyPathMapper
(getters/setters, arrays) • Ideas: • DomDocumentMapper (nodes of a DomDocument instance) • EAVMapper (attributes of EAV instances) -
Bernhard Schussek @webmozart 55/82 Example: Terms and Conditions $builder->add('terms', 'checkbox',
array( 'mapped' => false, )); order [form] Order Object terms [checkbox] model data $form->get('terms')->setData(true); $form->get('terms')->getData(); $builder->add('terms', 'checkbox', array( 'mapped' => false, 'data' => true, )); true -
Bernhard Schussek @webmozart 56/82 Example: Customized Getters/Setters $builder->add('name', 'text', array(
'property_path' => 'fullName', )); employee [form] Employee Object name [text] "Jane" $form->setData($empl->getFullName()) $empl->setFullName($form->getData()) -
Bernhard Schussek @webmozart 57/82 Example: Customized Getters/Setters $builder->add('name', 'text', array(
'property_path' => 'fullName', )); employee [form] Employee Object name [text] "Jane" $form->setData($empl->fullName) $empl->fullName = $form->getData() -
Bernhard Schussek @webmozart 58/82 Example: Array Access $builder->add('name', 'text', array(
'property_path' => '[fullName]', )); employee [form] Employee Object name [text] "Jane" $form->setData($empl['fullName']) $empl['fullName'] = $form->getData() -
Bernhard Schussek @webmozart 59/82 Example: Chained Getters/Setters $builder->add('name', 'text', array(
'property_path' => 'personalDetails.fullName', )); employee [form] Employee Object name [text] "Jane" $form->setData($empl->getPersonalDetails()->getFullName()) $empl->getPersonalDetails()->setFullName($form->getData()) -
Bernhard Schussek @webmozart 60/82 Example: Mixed Chaining $builder->add('name', 'text', array(
'property_path' => '[personalDetails].fullName', )); employee [form] Employee Object name [text] "Jane" $form->setData($empl['personalDetails']->getFullName()) $empl['personalDetails']->setFullName($form->getData()) -
Bernhard Schussek @webmozart 61/82 Example: Disable By-Reference Handling employee [form]
Employee Object address [form] Address Object street [text] "4th Street" city [text] "San Francisco" $address->setCity($form->getData()) $address->setStreet($form->getData()) $empl->setAddress($address) -
Bernhard Schussek @webmozart 62/82 Example: Disable By-Reference Handling employee [form]
Employee Object address [form] Address Object street [text] "4th Street" city [text] "San Francisco" $empl->setAddress($address) $builder->add('address', 'form', array( 'by_reference' => false, )); -
Bernhard Schussek @webmozart 63/82 publishedAt [date] year [choice] month [choice]
day [choice] DateTime Object model data model data model data setData($date->getYear()) setData($date) -
Bernhard Schussek @webmozart 64/82 publishedAt [date] year [choice] month [choice]
day [choice] DateTime Object model data model data model data array array setData($date['year']) 2012 setData($date['month']) 9 setData($date['day']) 27 setData($date) -
Bernhard Schussek @webmozart 66/82 Recap • Data Transformers • modification
of a data's representation • Data Mappers • data exchange between forms and fields • Missing Piece: Events • modification of a data's content (filtering, cleaning, ...) • dynamic form modification -
Bernhard Schussek @webmozart 69/82 projectName [text] string string string Example:
Filtering bind('Symfony'); "Symfony" "Symfony" "symfony" "symfony" BIND -
Bernhard Schussek @webmozart 70/82 "Symfony" "symfony" BIND "symfony" "symfony" projectName
[text] Example: Filtering bind('Symfony'); -
Bernhard Schussek @webmozart 71/82 Example: Filtering $builder->get('projectName')->addEventListener( FormEvents::BIND, function (FormEvent
$event) { $event->setData(strtolower($event->getData())); } ); -
Bernhard Schussek @webmozart 72/82 Example: Data-Dependent Forms • Employee in
a leading position: • Employee not in a leading position does not show this field. -
Bernhard Schussek @webmozart 73/82 class EmployeeType extends AbstractType { private
$employee; public function __construct(Employee $employee) { $this->employee = $employee; } public function buildForm(FormBuilderInterface $builder, array $options) { if ($this->employee->isInLeadingPosition()) { $builder->add('department', 'text'); } } } -
Bernhard Schussek @webmozart 74/82 class EmployeeType extends AbstractType { private
$employee; public function __construct(Employee $employee) { $this->employee = $employee; } public function buildForm(FormBuilderInterface $builder, array $options) { if ($this->employee->isInLeadingPosition()) { $builder->add('department', 'text'); } } } -
Bernhard Schussek @webmozart 75/82 Form Types vs. Instances • Form
Types • exist once in your application • only dependencies that are the same for every form instance (e.g. EntityManager) • Form Instances • exist multiple times with different data • data can (and often will) change after construction!!! -
Bernhard Schussek @webmozart 76/82 employee [form] object array object array
object array setData($empl) PRE_SET_DATA Employee Object Employee Object department Employee Object setData($empl->getDept()) -
Bernhard Schussek @webmozart 77/82 public function buildForm(FormBuilderInterface $builder, array $options)
{ $formFactory = $builder->getFormFactory(); $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formFactory) { if ($event->getData()->isInLeadingPosition()) { $event->getForm()->add( $formFactory->createNamed('department', 'text'); ); } } ); } -
Bernhard Schussek @webmozart 79/82 employee [form] model data norm data
view data setData($empl) PRE_SET_DATA Employee Employee Employee POST_SET_DATA -
Bernhard Schussek @webmozart 80/82 employee [form] bind(array(...)) PRE_BIND Employee Employee
Employee POST_BIND bind(...) BIND