Slide 1

Slide 1 text

Symfony2 Form Tricks

Slide 2

Slide 2 text

Bernhard Schussek @webmozart 2/82 Outline ● Introduction ● Data formats ● Data mapping ● Events

Slide 3

Slide 3 text

Bernhard Schussek @webmozart 3/82 Today's Example

Slide 4

Slide 4 text

Bernhard Schussek @webmozart 4/82 Form Form Form Form Form Form Symfony's Perspective Form Form Form Form Form Form Form Form

Slide 5

Slide 5 text

Bernhard Schussek @webmozart 5/82 Form Tree Form Form Form Form Form Form Form $factory = Forms::createFormFactory(); $form = $factory->createForm(...);

Slide 6

Slide 6 text

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();

Slide 7

Slide 7 text

Bernhard Schussek @webmozart 7/82 Forms Have Names month day year employee name salary birthDate $form->get('birthDate'); $form->get('birthDate')->get('year');

Slide 8

Slide 8 text

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();

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Bernhard Schussek @webmozart 10/82 Form Lifecycle New Prepopulated Bound setData($data) bind($data) bind($data) / setData(null) implicitly called

Slide 11

Slide 11 text

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);

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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);

Slide 14

Slide 14 text

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"

Slide 15

Slide 15 text

Bernhard Schussek @webmozart 15/82 http://www.flickr.com/photos/teegardin/5512347305/ Data Formats

Slide 16

Slide 16 text

Bernhard Schussek @webmozart 16/82 Data Formats Form model data view data

Slide 17

Slide 17 text

Bernhard Schussek @webmozart 17/82 Examples [text] string string

Slide 18

Slide 18 text

Bernhard Schussek @webmozart 18/82 Examples [number] float localized string

Slide 19

Slide 19 text

Bernhard Schussek @webmozart 19/82 Examples [date] string integer array DateTime string array $builder->add('createdAt', 'date', array( 'input' => 'array', 'widget' => 'single_text', ));

Slide 20

Slide 20 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(); } ); } }

Slide 21

Slide 21 text

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(); } );

Slide 22

Slide 22 text

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/

Slide 23

Slide 23 text

Bernhard Schussek @webmozart 23/82 Data Transformation Form model data view data normalized data model transformers view transformers setData($data)

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Bernhard Schussek @webmozart 25/82 Data Transformation ● Symfony\Component\Form\DataTransformerInterface ● bijective ● transform($data) ● reverseTransform($data)

Slide 26

Slide 26 text

Bernhard Schussek @webmozart 26/82 Example: ChoiceOrTextType

Slide 27

Slide 27 text

Bernhard Schussek @webmozart 27/82 Example: ChoiceOrTextType [choiceortext] string array string ValueToChoiceOrTextTransformer

Slide 28

Slide 28 text

Bernhard Schussek @webmozart 28/82 Tip http://www.flickr.com/photos/xverges/4622486563/ The normalized data should contain as much information as possible.

Slide 29

Slide 29 text

Bernhard Schussek @webmozart 29/82 Example: ChoiceOrTextType [choiceortext] string array array ValueToChoiceOrTextTransformer

Slide 30

Slide 30 text

Bernhard Schussek @webmozart 30/82 use Symfony\Component\Form\DataTransformerInterface; class ValueToChoiceOrTextTransformer implements DataTransformerInterface { public function transform($data) { ... } public function reverseTransform($data) { ... } } Transformer Implementation

Slide 31

Slide 31 text

Bernhard Schussek @webmozart 31/82 Case 1: Existing Team Selected

Slide 32

Slide 32 text

Bernhard Schussek @webmozart 32/82 Case 1: Existing Team Selected [choiceortext] model data view data normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', ));

Slide 33

Slide 33 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' => '', ));

Slide 34

Slide 34 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' => '', ));

Slide 35

Slide 35 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' => '', ));

Slide 36

Slide 36 text

Bernhard Schussek @webmozart 36/82 Case 2: New Team Created

Slide 37

Slide 37 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', ));

Slide 38

Slide 38 text

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', ));

Slide 39

Slide 39 text

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', ));

Slide 40

Slide 40 text

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', ));

Slide 41

Slide 41 text

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.

Slide 42

Slide 42 text

Bernhard Schussek @webmozart 42/82 Don't array( 'year' => 2012, 'month' => 9, 'day' => 27, ); array( 'year' => 2010, 'month' => 9, 'day' => 27, );

Slide 43

Slide 43 text

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 )

Slide 44

Slide 44 text

Bernhard Schussek @webmozart 44/82 Example: MyDateType [mydate] MyDateTime array string DateTime

Slide 45

Slide 45 text

Bernhard Schussek @webmozart 45/82 [mydate] array string DateTime [date] string integer array DateTime string integer array DateTime MyDateTime MyDateTimeToDateTimeTransformer

Slide 46

Slide 46 text

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);

Slide 47

Slide 47 text

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 ?

Slide 48

Slide 48 text

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 ?

Slide 49

Slide 49 text

Bernhard Schussek @webmozart 49/82 http://www.flickr.com/photos/wvs/701772889/ Data Mapping

Slide 50

Slide 50 text

Bernhard Schussek @webmozart 50/82 Data Mapping Data Mapping is the exchange of information between a form and its fields.

Slide 51

Slide 51 text

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"

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Bernhard Schussek @webmozart 53/82 Data Mapping ● Symfony\Component\Form\DataMapperInterface ● bijective ● mapFormsToData(array $forms, $data) ● mapDataToForms($data, array $forms)

Slide 54

Slide 54 text

Bernhard Schussek @webmozart 54/82 Implementations ● In Symfony: ● PropertyPathMapper (getters/setters, arrays) ● Ideas: ● DomDocumentMapper (nodes of a DomDocument instance) ● EAVMapper (attributes of EAV instances)

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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())

Slide 57

Slide 57 text

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()

Slide 58

Slide 58 text

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()

Slide 59

Slide 59 text

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())

Slide 60

Slide 60 text

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())

Slide 61

Slide 61 text

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)

Slide 62

Slide 62 text

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, ));

Slide 63

Slide 63 text

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)

Slide 64

Slide 64 text

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)

Slide 65

Slide 65 text

Bernhard Schussek @webmozart 65/82 http://www.flickr.com/photos/mom_smackley/4627430439/ ???

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Bernhard Schussek @webmozart 67/82 Events http://www.flickr.com/photos/karamell/4612302824/

Slide 68

Slide 68 text

Bernhard Schussek @webmozart 68/82 Example: Filtering convert to lowercase on submit

Slide 69

Slide 69 text

Bernhard Schussek @webmozart 69/82 projectName [text] string string string Example: Filtering bind('Symfony'); "Symfony" "Symfony" "symfony" "symfony" BIND

Slide 70

Slide 70 text

Bernhard Schussek @webmozart 70/82 "Symfony" "symfony" BIND "symfony" "symfony" projectName [text] Example: Filtering bind('Symfony');

Slide 71

Slide 71 text

Bernhard Schussek @webmozart 71/82 Example: Filtering $builder->get('projectName')->addEventListener( FormEvents::BIND, function (FormEvent $event) { $event->setData(strtolower($event->getData())); } );

Slide 72

Slide 72 text

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.

Slide 73

Slide 73 text

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'); } } }

Slide 74

Slide 74 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'); } } }

Slide 75

Slide 75 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!!!

Slide 76

Slide 76 text

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())

Slide 77

Slide 77 text

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'); ); } } ); }

Slide 78

Slide 78 text

Bernhard Schussek @webmozart 78/82 http://www.flickr.com/photos/freefoto/5982549938/ Summary

Slide 79

Slide 79 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

Slide 80

Slide 80 text

Bernhard Schussek @webmozart 80/82 employee [form] bind(array(...)) PRE_BIND Employee Employee Employee POST_BIND bind(...) BIND

Slide 81

Slide 81 text

Bernhard Schussek @webmozart 81/82 ?

Slide 82

Slide 82 text

Bernhard Schussek @webmozart 82/82 Thank you! https://joind.in/talk/view/7217 Bernhard Schussek @webmozart