Symfony2 Form Tricks

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.

24d20907afea0f684e62d620b886af16?s=128

Bernhard Schussek

September 28, 2012
Tweet

Transcript

  1. Symfony2 Form Tricks

  2. Bernhard Schussek @webmozart 2/82 Outline • Introduction • Data formats

    • Data mapping • Events
  3. Bernhard Schussek @webmozart 3/82 Today's Example

  4. Bernhard Schussek @webmozart 4/82 Form Form Form Form Form Form

    Symfony's Perspective Form Form Form Form Form Form Form Form
  5. Bernhard Schussek @webmozart 5/82 Form Tree Form Form Form Form

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

    employee name salary birthDate $form->get('birthDate'); $form->get('birthDate')->get('year');
  8. 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();
  9. 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
  10. Bernhard Schussek @webmozart 10/82 Form Lifecycle New Prepopulated Bound setData($data)

    bind($data) bind($data) / setData(null) implicitly called
  11. 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);
  12. 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
  13. 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);
  14. 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"
  15. Bernhard Schussek @webmozart 15/82 http://www.flickr.com/photos/teegardin/5512347305/ Data Formats

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

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

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

  19. Bernhard Schussek @webmozart 19/82 Examples [date] string integer array DateTime

    string array $builder->add('createdAt', 'date', array( 'input' => 'array', 'widget' => 'single_text', ));
  20. 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(); } ); } }
  21. 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(); } );
  22. 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/
  23. Bernhard Schussek @webmozart 23/82 Data Transformation Form model data view

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

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

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

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

    contain as much information as possible.
  29. Bernhard Schussek @webmozart 29/82 Example: ChoiceOrTextType [choiceortext] string array array

    ValueToChoiceOrTextTransformer
  30. Bernhard Schussek @webmozart 30/82 use Symfony\Component\Form\DataTransformerInterface; class ValueToChoiceOrTextTransformer implements DataTransformerInterface

    { public function transform($data) { ... } public function reverseTransform($data) { ... } } Transformer Implementation
  31. Bernhard Schussek @webmozart 31/82 Case 1: Existing Team Selected

  32. Bernhard Schussek @webmozart 32/82 Case 1: Existing Team Selected [choiceortext]

    model data view data normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', ));
  33. 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' => '', ));
  34. 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' => '', ));
  35. 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' => '', ));
  36. Bernhard Schussek @webmozart 36/82 Case 2: New Team Created

  37. 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', ));
  38. 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', ));
  39. 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', ));
  40. 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', ));
  41. 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.
  42. Bernhard Schussek @webmozart 42/82 Don't array( 'year' => 2012, 'month'

    => 9, 'day' => 27, ); array( 'year' => 2010, 'month' => 9, 'day' => 27, );
  43. 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 )
  44. Bernhard Schussek @webmozart 44/82 Example: MyDateType [mydate] MyDateTime array string

    DateTime
  45. Bernhard Schussek @webmozart 45/82 [mydate] array string DateTime [date] string

    integer array DateTime string integer array DateTime MyDateTime MyDateTimeToDateTimeTransformer
  46. 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);
  47. 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 ?
  48. 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 ?
  49. Bernhard Schussek @webmozart 49/82 http://www.flickr.com/photos/wvs/701772889/ Data Mapping

  50. Bernhard Schussek @webmozart 50/82 Data Mapping Data Mapping is the

    exchange of information between a form and its fields.
  51. 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"
  52. 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
  53. Bernhard Schussek @webmozart 53/82 Data Mapping • Symfony\Component\Form\DataMapperInterface • bijective

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

    (getters/setters, arrays) • Ideas: • DomDocumentMapper (nodes of a DomDocument instance) • EAVMapper (attributes of EAV instances)
  55. 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
  56. 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())
  57. 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()
  58. 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()
  59. 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())
  60. 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())
  61. 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)
  62. 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, ));
  63. 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)
  64. 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)
  65. Bernhard Schussek @webmozart 65/82 http://www.flickr.com/photos/mom_smackley/4627430439/ ???

  66. 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
  67. Bernhard Schussek @webmozart 67/82 Events http://www.flickr.com/photos/karamell/4612302824/

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

    submit
  69. Bernhard Schussek @webmozart 69/82 projectName [text] string string string Example:

    Filtering bind('Symfony'); "Symfony" "Symfony" "symfony" "symfony" BIND
  70. Bernhard Schussek @webmozart 70/82 "Symfony" "symfony" BIND "symfony" "symfony" projectName

    [text] Example: Filtering bind('Symfony');
  71. Bernhard Schussek @webmozart 71/82 Example: Filtering $builder->get('projectName')->addEventListener( FormEvents::BIND, function (FormEvent

    $event) { $event->setData(strtolower($event->getData())); } );
  72. 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.
  73. 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'); } } }
  74. 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'); } } }
  75. 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!!!
  76. 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())
  77. 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'); ); } } ); }
  78. Bernhard Schussek @webmozart 78/82 http://www.flickr.com/photos/freefoto/5982549938/ Summary

  79. Bernhard Schussek @webmozart 79/82 employee [form] model data norm data

    view data setData($empl) PRE_SET_DATA Employee Employee Employee POST_SET_DATA
  80. Bernhard Schussek @webmozart 80/82 employee [form] bind(array(...)) PRE_BIND Employee Employee

    Employee POST_BIND bind(...) BIND
  81. Bernhard Schussek @webmozart 81/82 ?

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