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. Symfony2 Form Tricks

    View full-size slide

  2. Bernhard Schussek @webmozart 2/82
    Outline

    Introduction

    Data formats

    Data mapping

    Events

    View full-size slide

  3. Bernhard Schussek @webmozart 3/82
    Today's Example

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

  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"

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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/

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  25. Bernhard Schussek @webmozart 25/82
    Data Transformation

    Symfony\Component\Form\DataTransformerInterface

    bijective

    transform($data)

    reverseTransform($data)

    View full-size slide

  26. Bernhard Schussek @webmozart 26/82
    Example: ChoiceOrTextType

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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.

    View full-size slide

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

    View full-size slide

  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
    )

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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
    ?

    View full-size slide

  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
    ?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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"

    View full-size slide

  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

    View full-size slide

  53. Bernhard Schussek @webmozart 53/82
    Data Mapping

    Symfony\Component\Form\DataMapperInterface

    bijective

    mapFormsToData(array $forms, $data)

    mapDataToForms($data, array $forms)

    View full-size slide

  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)

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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)

    View full-size slide

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

    View full-size slide

  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)

    View full-size slide

  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)

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

  81. Bernhard Schussek @webmozart 81/82
    ?

    View full-size slide

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

    View full-size slide