3 Steps to Symfony2 Form Mastery

3 Steps to Symfony2 Form Mastery

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 master the component in three simple steps.

24d20907afea0f684e62d620b886af16?s=128

Bernhard Schussek

November 23, 2012
Tweet

Transcript

  1. 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
  2. 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
  3. Bernhard Schussek @webmozart 3/101 Bernard @webmozart "that Forms guy"

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

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

  6. Austria

  7. 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
  8. Bernhard Schussek @webmozart 8/101

  9. Bernhard Schussek @webmozart 9/101

  10. Symfony Form Guru in 30 minutes

  11. Bernhard Schussek @webmozart 11/101 3 Simple Steps

  12. 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
  13. Bernhard Schussek @webmozart 13/101 Example

  14. Bernhard Schussek @webmozart 14/101 Today's Example

  15. Bernhard Schussek @webmozart 15/101 Form Form Form Form Form Form

    Form Symfony's Perspective Form Form Form Form Form Form Form
  16. Bernhard Schussek @webmozart 16/101 Form Tree Form Form Form Form

    Form Form Form $factory = Forms::createFormFactory(); $form = $factory->createForm(...);
  17. 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
  18. Bernhard Schussek @webmozart 18/101 Forms Have Names month day year

    employee name salary birthDate $form->get('birthDate'); $form->get('birthDate')->get('year'); 1
  19. 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
  20. 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
  21. Bernhard Schussek @webmozart 21/101 Form Lifecycle New Prepopulated Bound setData($data)

    submit($data) submit($data) / setData(null) implicitly called
  22. 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);
  23. 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
  24. 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);
  25. 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"
  26. Bernhard Schussek @webmozart 26/101 http://www.flickr.com/photos/yourdon/2573762303/ Step 1: Understand Data Formats

  27. Bernhard Schussek @webmozart 27/101 Formats who?

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

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

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

  31. Bernhard Schussek @webmozart 31/101 Examples [date] string integer array DateTime

    Object string array $builder->add('createdAt', 'date', array( 'input' => 'array', 'widget' => 'single_text', ));
  32. 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(); } ); } }
  33. 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(); } );
  34. 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/
  35. Bernhard Schussek @webmozart 35/101 In Practice

  36. Bernhard Schussek @webmozart 36/101 Data Transformation Form model data view

    data normalized data model transformers view transformers setData($data)
  37. 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
  38. Bernhard Schussek @webmozart 38/101 Data Transformation • Symfony\Component\Form\DataTransformerInterface • bijective

    • transform($data) • reverseTransform($data)
  39. Bernhard Schussek @webmozart 39/101 Example: ChoiceOrTextType

  40. Bernhard Schussek @webmozart 40/101 Example: ChoiceOrTextType [choiceortext] string array string

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

    ValueToChoiceOrTextTransformer
  42. Bernhard Schussek @webmozart 42/101 Tip http://www.flickr.com/photos/xverges/4622486563/ The normalized data should

    contain as much information as possible.
  43. Bernhard Schussek @webmozart 43/101 Case 1: Existing Team Selected

  44. 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" => "", )
  45. Bernhard Schussek @webmozart 45/101 Case 2: New Team Created

  46. 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", )
  47. Bernhard Schussek @webmozart 47/101 use Symfony\Component\Form\DataTransformerInterface; class ValueToChoiceOrTextTransformer implements DataTransformerInterface

    { public function transform($data) { ... } public function reverseTransform($data) { ... } } Transformer Implementation
  48. Bernhard Schussek @webmozart 48/101 Game: Right or Wrong?

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

  50. Bernhard Schussek @webmozart 50/101 Right or Wrong? 'SYmfony' 'symfony' not

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

    bijective
  52. 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
  53. 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 ...
  54. Bernhard Schussek @webmozart 54/101 Transformers and Type Reuse

  55. Bernhard Schussek @webmozart 55/101 Example: CustomMoneyType [custommoney] Money Object string

    float custom same as in MoneyType
  56. Bernhard Schussek @webmozart 56/101 Money Object MoneyToFloatTransformer [custommoney] string float

    [money] float integer
  57. Bernhard Schussek @webmozart 57/101 Back to the start...

  58. 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);
  59. 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 ?
  60. 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 ?
  61. Bernhard Schussek @webmozart 61/101 http://www.flickr.com/photos/brandoncwarren/4164759025 Step 2: Become Friends with

    Data Mappers
  62. Bernhard Schussek @webmozart 62/101 Data what?

  63. Bernhard Schussek @webmozart 63/101 Data Mapping Data Mapping is the

    exchange of information between a form's data and its fields.
  64. 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"
  65. 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
  66. Bernhard Schussek @webmozart 66/101 Data Mapping • Symfony\Component\Form\DataMapperInterface • bijective

    • mapFormsToData(array $forms, $data) • mapDataToForms($data, array $forms)
  67. Bernhard Schussek @webmozart 67/101 Implementations • In Symfony: • PropertyPathMapper

    (getters/setters, arrays) • Ideas: • DomDocumentMapper (nodes of a DomDocument instance) • EAVMapper (attributes of EAV instances)
  68. Bernhard Schussek @webmozart 68/101 Yeah, but...

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

  70. 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
  71. Bernhard Schussek @webmozart 71/101 Yeah, but... Customized Getters/Setters 2

  72. 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())
  73. 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()
  74. 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()
  75. 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())
  76. 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())
  77. Bernhard Schussek @webmozart 77/101 Yeah, but... By-Reference Handling 3

  78. 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)
  79. 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, ));
  80. Bernhard Schussek @webmozart 80/101 Yeah, but... Non-Existent Accessors 4

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

  84. 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!
  85. Bernhard Schussek @webmozart 85/101 Step 3: Events http://www.sxc.hu/photo/813792

  86. Bernhard Schussek @webmozart 86/101 Data Filtering

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

    submit
  88. Bernhard Schussek @webmozart 88/101 projectName [text] string string string Data

    Filtering submit('Symfony'); "Symfony" "Symfony" "symfony" "symfony" SUBMIT
  89. Bernhard Schussek @webmozart 89/101 Data Filtering $builder->get('projectName')->addEventListener( FormEvents::SUBMIT, function (FormEvent

    $event) { $event->setData(strtolower($event->getData())); } );
  90. Bernhard Schussek @webmozart 90/101 Data-Dependent Forms

  91. Bernhard Schussek @webmozart 91/101 Data-Dependent Forms • Employee in a

    leading position: • Employee not in a leading position does not show this field.
  92. 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'); } } }
  93. 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'); } } }
  94. 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!!!
  95. 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())
  96. 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'); } } ); }
  97. Bernhard Schussek @webmozart 97/101 http://www.flickr.com/photos/74576085@N00/5428693024/ All together now

  98. Bernhard Schussek @webmozart 98/101 employee [form] model data norm data

    view data setData($empl) PRE_SET_DATA Employee Employee Employee POST_SET_DATA
  99. Bernhard Schussek @webmozart 99/101 employee [form] submit(array(...)) PRE_SUBMIT Employee Employee

    Employee POST_SUBMIT submit(...) SUBMIT
  100. Bernhard Schussek @webmozart 100/101 That's it. Have fun! :)

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

    you!