Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Bernhard Schussek

September 28, 2012
Tweet

More Decks by Bernhard Schussek

Other Decks in Programming

Transcript

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

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

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

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

    bind($data) bind($data) / setData(null) implicitly called
  8. 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);
  9. 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
  10. 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);
  11. 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"
  12. Bernhard Schussek @webmozart 19/82 Examples [date] string integer array DateTime

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

    data normalized data model transformers view transformers setData($data)
  17. 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
  18. Bernhard Schussek @webmozart 30/82 use Symfony\Component\Form\DataTransformerInterface; class ValueToChoiceOrTextTransformer implements DataTransformerInterface

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

    model data view data normalized data ValueToChoiceOrTextTransformer bind(array( 'choice' => 'Team 1', 'text' => '', ));
  20. 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' => '', ));
  21. 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' => '', ));
  22. 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' => '', ));
  23. 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', ));
  24. 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', ));
  25. 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', ));
  26. 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', ));
  27. 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.
  28. Bernhard Schussek @webmozart 42/82 Don't array( 'year' => 2012, 'month'

    => 9, 'day' => 27, ); array( 'year' => 2010, 'month' => 9, 'day' => 27, );
  29. 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 )
  30. Bernhard Schussek @webmozart 45/82 [mydate] array string DateTime [date] string

    integer array DateTime string integer array DateTime MyDateTime MyDateTimeToDateTimeTransformer
  31. 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);
  32. 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 ?
  33. 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 ?
  34. Bernhard Schussek @webmozart 50/82 Data Mapping Data Mapping is the

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

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

    (getters/setters, arrays) • Ideas: • DomDocumentMapper (nodes of a DomDocument instance) • EAVMapper (attributes of EAV instances)
  39. 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
  40. 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())
  41. 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()
  42. 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()
  43. 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())
  44. 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())
  45. 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)
  46. 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, ));
  47. 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)
  48. 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)
  49. 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
  50. Bernhard Schussek @webmozart 69/82 projectName [text] string string string Example:

    Filtering bind('Symfony'); "Symfony" "Symfony" "symfony" "symfony" BIND
  51. 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.
  52. 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'); } } }
  53. 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'); } } }
  54. 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!!!
  55. 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())
  56. 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'); ); } } ); }
  57. Bernhard Schussek @webmozart 79/82 employee [form] model data norm data

    view data setData($empl) PRE_SET_DATA Employee Employee Employee POST_SET_DATA