Slide 1

Slide 1 text

3 Steps 3 Steps to Symfony2 Form Mastery to Symfony2 Form Mastery Bernhard Bernhard Schussek aka @webmozart Schussek aka @webmozart SymfonyDay Portugal SymfonyDay Portugal March 8th, 2014 March 8th, 2014

Slide 2

Slide 2 text

Bernhard Schussek @webmozart 2/101 Bernhard Schussek Not: Bernard Bernhart Bernardt Bernharth Also Wrong: Schusseck Schusek Shusseck Scussek Shcusek Schuseck Shusek Schuhsek Shuhseck Shushek Shuseck Shusec Shushek Shushek Shushec Sussek Shussec Schußeck Scusek Shußec Schuhsec Shußec Schushek Schusheck

Slide 3

Slide 3 text

Bernhard Schussek @webmozart 3/101 Bernard @webmozart "that Forms guy"

Slide 4

Slide 4 text

Bernhard Schussek @webmozart 4/101 http://commons.wikimedia.org/wiki/File%3AAustralia-New_Guinea_(orthographic_projection).svg

Slide 5

Slide 5 text

Bernhard Schussek @webmozart 5/101 http://upload.wikimedia.org/wikipedia/commons/b/b3/Blank_map_of_Europe.svg

Slide 6

Slide 6 text

Austria

Slide 7

Slide 7 text

Bernhard Schussek @webmozart 7/101 flag of Austria not the flag of Austria http://en.wikipedia.org/wiki/File:Flag_of_Austria.svg http://en.wikipedia.org/wiki/File:Zeichen_267.svg

Slide 8

Slide 8 text

Bernhard Schussek @webmozart 8/101

Slide 9

Slide 9 text

Bernhard Schussek @webmozart 9/101

Slide 10

Slide 10 text

Symfony Form Guru in 30 minutes

Slide 11

Slide 11 text

Bernhard Schussek @webmozart 11/101 3 Simple Steps

Slide 12

Slide 12 text

Bernhard Schussek @webmozart 12/101 Outline ● Today's Example ● Step 1: Understand Data Formats ● Step 2: Become Friends with Data Mappers ● Step 3: Get Ready for the Event

Slide 13

Slide 13 text

Bernhard Schussek @webmozart 13/101 Example

Slide 14

Slide 14 text

Bernhard Schussek @webmozart 14/101 Today's Example

Slide 15

Slide 15 text

Bernhard Schussek @webmozart 15/101 Form Form Form Form Form Form Form Symfony's Perspective Form Form Form Form Form Form Form

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Bernhard Schussek @webmozart 17/101 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(); 1

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Bernhard Schussek @webmozart 19/101 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(); 2

Slide 20

Slide 20 text

Bernhard Schussek @webmozart 20/101 Forms Have Data employee [form] data name [text] data salary [money] data birthDate [birthdate] data day [choice] data month [choice] data year [choice] data 3

Slide 21

Slide 21 text

Bernhard Schussek @webmozart 21/101 Form Lifecycle New Prepopulated Bound setData($data) submit($data) submit($data) / setData(null) implicitly called

Slide 22

Slide 22 text

Bernhard Schussek @webmozart 22/101 Prepopulation employee [form] data name [text] data salary [money] data birthDate [birthdate] data year [choice] data month [choice] data day [choice] data $employee = new Employee(); $employee->name = 'Jane'; $employee->salary = 110000.0; $employee->birthDate = new DateTime('1941-09-09'); $form->setData($employee);

Slide 23

Slide 23 text

Bernhard Schussek @webmozart 23/101 Prepopulation employee [form] Employee Object name [text] salary [money] birthDate [birthdate] year [choice] month [choice] day [choice] data data data "Jane" 110000.0 DateTime Object data data data 1941 9 9

Slide 24

Slide 24 text

Bernhard Schussek @webmozart 24/101 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->handleRequest($request);

Slide 25

Slide 25 text

Bernhard Schussek @webmozart 25/101 ? employee [form] Employee Object name [text] "Jane" salary [money] 110000.0 birthDate [birthdate] DateTime Object year [choice] 1941 month [choice] 9 day [choice] 9 "1962" "95000.00" "5" "26" "John"

Slide 26

Slide 26 text

Bernhard Schussek @webmozart 26/101 http://www.flickr.com/photos/yourdon/2573762303/ Step 1: Understand Data Formats

Slide 27

Slide 27 text

Bernhard Schussek @webmozart 27/101 Formats who?

Slide 28

Slide 28 text

Bernhard Schussek @webmozart 28/101 Data Formats Form model data view data

Slide 29

Slide 29 text

Bernhard Schussek @webmozart 29/101 Examples [text] string string

Slide 30

Slide 30 text

Bernhard Schussek @webmozart 30/101 Examples [number] float localized string

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Bernhard Schussek @webmozart 32/101 Which format do I get? class MyDateType extends AbstractType { public function getParent() { return 'date'; } public function buildForm(FormBuilderInterface $builder) { $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { $data = $event->getForm()->getData(); } ); } }

Slide 33

Slide 33 text

Bernhard Schussek @webmozart 33/101 Normalized Format [date] string integer array DateTime Object string array DateTime Object $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { $data = $event->getForm()->getNormData(); } );

Slide 34

Slide 34 text

Bernhard Schussek @webmozart 34/101 Tip ● Whenever you create a custom type, decide: ● Model format? ● Normalized format? ● View format? http://www.flickr.com/photos/xverges/4622486563/

Slide 35

Slide 35 text

Bernhard Schussek @webmozart 35/101 In Practice

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Bernhard Schussek @webmozart 37/101 Data Transformation Form model data view data normalized data model transformers view transformers submit($data) submit fields and map to view data

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Bernhard Schussek @webmozart 39/101 Example: ChoiceOrTextType

Slide 40

Slide 40 text

Bernhard Schussek @webmozart 40/101 Example: ChoiceOrTextType [choiceortext] string array string ValueToChoiceOrTextTransformer Was the value submitted through the choice or the text field?

Slide 41

Slide 41 text

Bernhard Schussek @webmozart 41/101 Example: ChoiceOrTextType [choiceortext] string array array ValueToChoiceOrTextTransformer

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Bernhard Schussek @webmozart 43/101 Case 1: Existing Team Selected

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Bernhard Schussek @webmozart 45/101 Case 2: New Team Created

Slide 46

Slide 46 text

Bernhard Schussek @webmozart 46/101 Case 2: New Team Created [choiceortext] model data view data normalized data ValueToChoiceOrTextTransformer submit(array( 'choice' => 'Other', 'text' => 'New Team', )); "New Team" array( "choice" => "Other", "text" => "New Team", ) array( "choice" => "Other", "text" => "New Team", )

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Bernhard Schussek @webmozart 48/101 Game: Right or Wrong?

Slide 49

Slide 49 text

Bernhard Schussek @webmozart 49/101 Right or Wrong? 'Symfony' bijective 'ynofmyS'

Slide 50

Slide 50 text

Bernhard Schussek @webmozart 50/101 Right or Wrong? 'SYmfony' 'symfony' not bijective! Solution: Events

Slide 51

Slide 51 text

Bernhard Schussek @webmozart 51/101 Right or Wrong? ['S','Y','m','f', 'o','n','y'] 'SYmfony' bijective

Slide 52

Slide 52 text

Bernhard Schussek @webmozart 52/101 Tip http://www.flickr.com/photos/xverges/4622486563/ Data transformers must always be bijective. A == reverseTransform(transform(A)) In practice: changing the representation string ↔ integer array ↔ DateTime

Slide 53

Slide 53 text

Bernhard Schussek @webmozart 53/101 Tip http://www.flickr.com/photos/xverges/4622486563/ To make one-way changes, use events. In practice: changing the content filtering sanitization ...

Slide 54

Slide 54 text

Bernhard Schussek @webmozart 54/101 Transformers and Type Reuse

Slide 55

Slide 55 text

Bernhard Schussek @webmozart 55/101 Example: CustomMoneyType [custommoney] Money Object string float custom same as in MoneyType

Slide 56

Slide 56 text

Bernhard Schussek @webmozart 56/101 Money Object MoneyToFloatTransformer [custommoney] string float [money] float integer

Slide 57

Slide 57 text

Bernhard Schussek @webmozart 57/101 Back to the start...

Slide 58

Slide 58 text

Bernhard Schussek @webmozart 58/101 Prepopulation employee [form] data name [text] data salary [money] data birthDate [birthdate] data year [choice] data month [choice] data day [choice] data $employee = new Employee(); $employee->name = 'Jane'; $employee->salary = 110000.0; $employee->birthDate = new DateTime('1941-09-09'); $form->setData($employee);

Slide 59

Slide 59 text

Bernhard Schussek @webmozart 59/101 Prepopulation employee [form] Employee Object name [text] model data salary [money] model data birthDate [birthdate] model data year [choice] model data month [choice] model data day [choice] model data ?

Slide 60

Slide 60 text

Bernhard Schussek @webmozart 60/101 Submission employee [form] Employee Object name [text] "John" salary [money] 95000.0 birthDate [birthdate] DateTime Object year [choice] 1962 month [choice] 5 day [choice] 18 ?

Slide 61

Slide 61 text

Bernhard Schussek @webmozart 61/101 http://www.flickr.com/photos/brandoncwarren/4164759025 Step 2: Become Friends with Data Mappers

Slide 62

Slide 62 text

Bernhard Schussek @webmozart 62/101 Data what?

Slide 63

Slide 63 text

Bernhard Schussek @webmozart 63/101 Data Mapping Data Mapping is the exchange of information between a form's data and its fields.

Slide 64

Slide 64 text

Bernhard Schussek @webmozart 64/101 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 65

Slide 65 text

Bernhard Schussek @webmozart 65/101 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()) handleRequest($request) "John" 95000.0 DateTime Object

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Bernhard Schussek @webmozart 68/101 Yeah, but...

Slide 69

Slide 69 text

Bernhard Schussek @webmozart 69/101 Yeah, but... Terms and Conditions 1

Slide 70

Slide 70 text

Bernhard Schussek @webmozart 70/101 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 71

Slide 71 text

Bernhard Schussek @webmozart 71/101 Yeah, but... Customized Getters/Setters 2

Slide 72

Slide 72 text

Bernhard Schussek @webmozart 72/101 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 73

Slide 73 text

Bernhard Schussek @webmozart 73/101 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 74

Slide 74 text

Bernhard Schussek @webmozart 74/101 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 75

Slide 75 text

Bernhard Schussek @webmozart 75/101 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 76

Slide 76 text

Bernhard Schussek @webmozart 76/101 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 77

Slide 77 text

Bernhard Schussek @webmozart 77/101 Yeah, but... By-Reference Handling 3

Slide 78

Slide 78 text

Bernhard Schussek @webmozart 78/101 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 79

Slide 79 text

Bernhard Schussek @webmozart 79/101 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 80

Slide 80 text

Bernhard Schussek @webmozart 80/101 Yeah, but... Non-Existent Accessors 4

Slide 81

Slide 81 text

Bernhard Schussek @webmozart 81/101 publishedAt [date] year [choice] month [choice] day [choice] DateTime Object model data model data model data setData($date->getYear()) setData($date)

Slide 82

Slide 82 text

Bernhard Schussek @webmozart 82/101 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 83

Slide 83 text

Bernhard Schussek @webmozart 83/101 Recap

Slide 84

Slide 84 text

Bernhard Schussek @webmozart 84/101 Recap ● Data Transformers ● change the data's representation ● Data Mappers ● exchange data between forms and fields ● Missing Piece: Events ● change the data's content ● dynamic forms!

Slide 85

Slide 85 text

Bernhard Schussek @webmozart 85/101 Step 3: Events http://www.sxc.hu/photo/813792

Slide 86

Slide 86 text

Bernhard Schussek @webmozart 86/101 Data Filtering

Slide 87

Slide 87 text

Bernhard Schussek @webmozart 87/101 Data Filtering convert to lowercase on submit

Slide 88

Slide 88 text

Bernhard Schussek @webmozart 88/101 projectName [text] string string string Data Filtering submit('Symfony'); "Symfony" "Symfony" "symfony" "symfony" SUBMIT

Slide 89

Slide 89 text

Bernhard Schussek @webmozart 89/101 Data Filtering $builder->get('projectName')->addEventListener( FormEvents::SUBMIT, function (FormEvent $event) { $event->setData(strtolower($event->getData())); } );

Slide 90

Slide 90 text

Bernhard Schussek @webmozart 90/101 Data-Dependent Forms

Slide 91

Slide 91 text

Bernhard Schussek @webmozart 91/101 Data-Dependent Forms ● Employee in a leading position: ● Employee not in a leading position does not show this field.

Slide 92

Slide 92 text

Bernhard Schussek @webmozart 92/101 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 93

Slide 93 text

Bernhard Schussek @webmozart 93/101 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 94

Slide 94 text

Bernhard Schussek @webmozart 94/101 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 95

Slide 95 text

Bernhard Schussek @webmozart 95/101 employee [form] object array object array object array setData($empl) PRE_SET_DATA Employee Object Employee Object department Employee Object setData($empl->getDept())

Slide 96

Slide 96 text

Bernhard Schussek @webmozart 96/101 public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event) { if ($event->getData()->isInLeadingPosition()) { $event->getForm()->add('department', 'text'); } } ); }

Slide 97

Slide 97 text

Bernhard Schussek @webmozart 97/101 http://www.flickr.com/photos/74576085@N00/5428693024/ All together now

Slide 98

Slide 98 text

Bernhard Schussek @webmozart 98/101 employee [form] model data norm data view data setData($empl) PRE_SET_DATA Employee Employee Employee POST_SET_DATA

Slide 99

Slide 99 text

Bernhard Schussek @webmozart 99/101 employee [form] submit(array(...)) PRE_SUBMIT Employee Employee Employee POST_SUBMIT submit(...) SUBMIT

Slide 100

Slide 100 text

Bernhard Schussek @webmozart 100/101 That's it. Have fun! :)

Slide 101

Slide 101 text

Bernhard Schussek @webmozart 101/101 Questions? http://joind.in/talk/view/10786 Bernhard Schussek @webmozart Thank you!