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

Symfony2 Forms: Do's and Don'ts

Symfony2 Forms: Do's and Don'ts

The Symfony Form component is one of the most complex components of the whole framework and often leaves developers startled. Let me give you a few guidelines that make development of forms much easier than you would have thought.

Bernhard Schussek

April 29, 2016
Tweet

More Decks by Bernhard Schussek

Other Decks in Programming

Transcript

  1. Bernhard Schussek · webmozart.io 2/122
    http://slightlyviral.com/23-examples-of-why-women-live-longer-than-men/
    Symfony2 Forms
    Symfony2 Forms
    Dos And Don'ts
    Dos And Don'ts
    Bernhard Schussek (@webmozart)
    Bernhard Schussek (@webmozart)
    Symfony Live Köln 2015
    Symfony Live Köln 2015

    View Slide

  2. Bernhard Schussek · webmozart.io 5/122
    Bernhard Schussek
    Freelancer, Trainer and Coach
    Symfony
    Architecture
    Coding Practices
    webmozart.io

    View Slide

  3. Bernhard Schussek · webmozart.io 6/122
    @webmozart

    View Slide

  4. Bernhard Schussek · webmozart.io 8/122
    What's the hardest part
    of Symfony?

    View Slide

  5. Bernhard Schussek · webmozart.io 9/122

    View Slide

  6. Bernhard Schussek · webmozart.io 10/122

    View Slide

  7. Bernhard Schussek · webmozart.io 11/122

    View Slide

  8. Bernhard Schussek · webmozart.io 12/122

    View Slide

  9. Bernhard Schussek · webmozart.io 13/122
    Make simple cases simple,
    make complex cases possible

    View Slide

  10. Bernhard Schussek · webmozart.io 14/122
    New in 2.7/2.8/3.0

    View Slide

  11. Bernhard Schussek · webmozart.io 15/122
    No More Type Names
    (>= 2.8)

    View Slide

  12. Bernhard Schussek · webmozart.io 16/122
    $form = $this->createForm(new PlaceOrderType());
    $form = $this->createForm('app_place_order');

    View Slide

  13. Bernhard Schussek · webmozart.io 17/122
    $form = $this->createForm(PlaceOrderType::class);

    View Slide

  14. Bernhard Schussek · webmozart.io 18/122
    $builder
    ->add('firstName', 'text')
    ->add('lastName', 'text')
    ->add('address', 'app_address')
    ->add('shippingMethod', 'entity', [
    // ...
    ])
    ;

    View Slide

  15. Bernhard Schussek · webmozart.io 19/122
    $builder
    ->add('firstName', TextType::class)
    ->add('lastName', TextType::class)
    ->add('address', AddressType::class)
    ->add('shippingMethod', EntityType::class, [
    // ...
    ])
    ;

    View Slide

  16. Bernhard Schussek · webmozart.io 20/122

    View Slide

  17. Bernhard Schussek · webmozart.io 21/122

    View Slide

  18. Bernhard Schussek · webmozart.io 22/122
    class PlaceOrderType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $buil
    {
    // ...
    }
    public function getName()
    {
    return 'app_place_order';
    }
    }

    View Slide

  19. Bernhard Schussek · webmozart.io 23/122
    class PlaceOrderType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $buil
    {
    // ...
    }
    }

    View Slide

  20. Bernhard Schussek · webmozart.io 24/122
    app.form.place_order:
    class: AppBundle\Form\PlaceOrderType
    tags:
    - { name: form.type, alias: app_place_order }
    app.form.submit_payment:
    class: AppBundle\Form\SubmitPaymentType
    tags:
    - { name: form.type, alias: app_submit_payment }

    View Slide

  21. Bernhard Schussek · webmozart.io 25/122
    app.form.process_order:
    class: AppBundle\Form\ProcessOrderType
    arguments:
    - @app.shipping_service
    tags:
    - { name: form.type, alias: app_process_order }

    View Slide

  22. Bernhard Schussek · webmozart.io 26/122
    app.form.process_order:
    class: AppBundle\Form\ProcessOrderType
    arguments:
    - @app.shipping_service
    tags:
    - { name: form.type }

    View Slide

  23. Bernhard Schussek · webmozart.io 27/122
    {% block app_place_order_widget %}
    {# ... #}
    {% endblock %}
    class PlaceOrderType extends AbstractType
    {
    // ...
    }

    View Slide

  24. Bernhard Schussek · webmozart.io 28/122
    {% block place_order_widget %}
    {# ... #}
    {% endblock %}
    class PlaceOrderType extends AbstractType
    {
    // ...
    }

    View Slide

  25. Bernhard Schussek · webmozart.io 29/122
    {% block app_place_order_widget %}
    {# ... #}
    {% endblock %}
    class PlaceOrderType extends AbstractType
    {
    // ...
    public function getBlockPrefix()
    {
    return 'app_place_order';
    }
    }

    View Slide

  26. Bernhard Schussek · webmozart.io 30/122
    configureOptions()
    (>= 2.7)

    View Slide

  27. Bernhard Schussek · webmozart.io 31/122
    class PlaceOrderType extends AbstractType
    {
    public function setDefaultOptions(
    OptionsResolverInterface $resolver)
    {
    // ...
    }
    }

    View Slide

  28. Bernhard Schussek · webmozart.io 32/122
    class PlaceOrderType extends AbstractType
    {
    public function configureOptions(
    OptionsResolver $resolver)
    {
    // ...
    }
    }

    View Slide

  29. Bernhard Schussek · webmozart.io 33/122
    $resolver->setRequired('payment_method');
    $resolver->setAllowedTypes('is_payable', 'bool');
    $resolver->setAllowedValues(
    'payment_method',
    PaymentMethod::all()
    );
    // ...

    View Slide

  30. Bernhard Schussek · webmozart.io 34/122
    "prototype_data" option
    (>= 2.8)

    View Slide

  31. Bernhard Schussek · webmozart.io 35/122
    $builder->add('lineItems', CollectionType::class, [
    'type' => OrderLineType::class,
    'prototype_data' => new OrderLine(
    'New item',
    120.00
    ),
    ]);

    View Slide

  32. Bernhard Schussek · webmozart.io 36/122
    Return null from
    "query_builder"
    (>= 2.8)

    View Slide

  33. Bernhard Schussek · webmozart.io 37/122
    $builder->add('shippingMethod', EntityType::class, [
    'class' => ShippingMethod::class,
    'query_builder' => function ($repo) use ($address) {
    if (!$address) {
    return null;
    }
    return $repo->queryByAddress($address);
    ),
    ]);

    View Slide

  34. Bernhard Schussek · webmozart.io 38/122
    Flipped "choices" option
    (>= 2.7)

    View Slide

  35. Bernhard Schussek · webmozart.io 39/122
    $builder->add('expressShipping', ChoiceType::class, [
    'choices' => [
    '' => 'Decide later',
    0 => 'No',
    1 => 'Yes',
    ],
    ]);

    View Slide

  36. Bernhard Schussek · webmozart.io 40/122
    $builder->add('expressShipping', ChoiceType::class, [
    'choices' => [
    'Decide later' => null,
    'No' => false,
    'Yes' => true,
    ],
    ]);

    View Slide

  37. Bernhard Schussek · webmozart.io 41/122
    $builder->add('expressShipping', ChoiceType::class, [
    'choices' => [
    'Decide later' => Selection::get(LATER),
    'No' => Selection::get(NO),
    'Yes' => Selection::get(YES),
    ],
    ]);

    View Slide

  38. Bernhard Schussek · webmozart.io 42/122
    $builder->add('expressShipping', ChoiceType::class, [
    'choices' => [
    'Decide later' => Selection::get(LATER),
    'No' => Selection::get(NO),
    'Yes' => Selection::get(YES),
    ],
    'choices_as_values' => true,
    ]);
    2.8 only

    View Slide

  39. Bernhard Schussek · webmozart.io 43/122
    New "choice_label" option
    (>= 2.7)

    View Slide

  40. Bernhard Schussek · webmozart.io 44/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    // $shipping->getTitle()
    'choice_label' => 'title',
    ]);

    Decide later
    No
    Yes

    View Slide

  41. Bernhard Schussek · webmozart.io 45/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    'choice_label' => function (Selection $selection) {
    return humanize($selection->getType());
    },
    ]);

    Decide later
    No
    Yes

    View Slide

  42. Bernhard Schussek · webmozart.io 46/122
    New "choice_value" option
    (>= 2.7)

    View Slide

  43. Bernhard Schussek · webmozart.io 47/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    ]);

    Decide later
    No
    Yes

    View Slide

  44. Bernhard Schussek · webmozart.io 48/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    // $selection->getType()
    'choice_value' => 'type'
    ]);

    Decide later
    No
    Yes

    View Slide

  45. Bernhard Schussek · webmozart.io 49/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    'choice_value' => function (Selection $selection) {
    return strtoupper($selection->getType());
    },
    ]);

    Decide later
    No
    Yes

    View Slide

  46. Bernhard Schussek · webmozart.io 50/122
    New "choice_attr" option
    (>= 2.7)

    View Slide

  47. Bernhard Schussek · webmozart.io 51/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    'choice_attr' => ['data-reference' => 1],
    ]);

    Decide later
    No
    Yes

    View Slide

  48. Bernhard Schussek · webmozart.io 52/122
    $builder->add('expressShipping', ChoiceType::class, [
    // ...
    'choice_attr' => function (Selection $selection) {
    return ['data-reference' => $selection->getRef()
    },
    ]);

    Decide later
    No
    Yes

    View Slide

  49. Bernhard Schussek · webmozart.io 53/122
    New "group_by" option
    (>= 2.7)

    View Slide

  50. Bernhard Schussek · webmozart.io 54/122
    $builder->add('paymentMethod', ChoiceType::class, [
    'choices' => [
    'Credit Card' => [
    'VISA' => PaymentMethod::VISA,
    'MasterCard' => PaymentMethod::MASTER_CARD,
    ],
    'Bank Transfer' => [
    // ...
    ],
    ],
    ]);


    ...

    ...

    View Slide

  51. Bernhard Schussek · webmozart.io 55/122
    $builder->add('paymentMethod', ChoiceType::class, [
    // ...
    // $paymentMethod->getCategory()
    'group_by' => 'category',
    ]);


    ...

    ...

    View Slide

  52. Bernhard Schussek · webmozart.io 56/122
    $builder->add('paymentMethod', ChoiceType::class, [
    // ...
    // $paymentMethod->getCategory()->getName()
    'group_by' => 'category.name',
    ]);


    ...

    ...

    View Slide

  53. Bernhard Schussek · webmozart.io 57/122
    $builder->add('paymentMethod', ChoiceType::class, [
    // ...
    'group_by' => function (PaymentMethod $method) {
    return sprintf(
    '%s (%s)',
    $method->getCategory()->getName(),
    $method->getCategory()->getFees()
    );
    },
    ]);


    ...

    ...

    View Slide

  54. Bernhard Schussek · webmozart.io 58/122
    Forms are not as hard as you think
    (even in 2.7)

    View Slide

  55. Bernhard Schussek · webmozart.io 59/122
    PlaceOrderType
    Controller
    FormFactory
    createForm() buildForm()
    FormBuilder
    Form

    View Slide

  56. Bernhard Schussek · webmozart.io 60/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    FormBuilder
    Form
    Order
    FormView
    update
    validate()

    View Slide

  57. Bernhard Schussek · webmozart.io 61/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  58. Bernhard Schussek · webmozart.io 62/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  59. Bernhard Schussek · webmozart.io 63/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  60. Bernhard Schussek · webmozart.io 64/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  61. Bernhard Schussek · webmozart.io 65/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  62. Bernhard Schussek · webmozart.io 66/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  63. Bernhard Schussek · webmozart.io 67/122
    My Best Practices

    View Slide

  64. Bernhard Schussek · webmozart.io 68/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  65. Bernhard Schussek · webmozart.io 69/122
    Where to Create Forms?
    In the Controller Custom Form Type
    Custom Form Type

    View Slide

  66. Bernhard Schussek · webmozart.io 70/122
    How to Create Form Types?
    doctrine:generate:form by hand
    by hand

    View Slide

  67. Bernhard Schussek · webmozart.io 71/122
    ✓ Use PhpStorm

    View Slide

  68. Bernhard Schussek · webmozart.io 72/122
    Where to Submit Forms to?
    Same Action Different Action
    Same Action

    View Slide

  69. Bernhard Schussek · webmozart.io 73/122
    $form = $this->createForm(PaymentType::class, $order, [
    'action' => $this->generateUrl('payment_gw'),
    ]);
    ✓ Use "action" option in the controller

    View Slide

  70. Bernhard Schussek · webmozart.io 74/122
    $form = $this->createForm(PaymentType::class, $order, [
    'method' => 'PUT',
    ]);
    ✓ Use "method" option in the controller

    View Slide

  71. Bernhard Schussek · webmozart.io 75/122
    $defaultShipping = $this->getDefaultShipping($customer);
    $form = $this->createForm(
    new PlaceOrderType($defaultShipping),
    );
    ✗ Don't Pass Dynamic Data to Constructor

    View Slide

  72. Bernhard Schussek · webmozart.io 76/122
    $defaultShipping = $this->getDefaultShipping($customer);
    $form = $this->createForm(PlaceOrderType::class, .., [
    'default_shipping' => $defaultShipping,
    ]);
    ✓ Use Custom Options

    View Slide

  73. Bernhard Schussek · webmozart.io 77/122
    parameters:
    app.us_shipping.enabled: true
    services:
    app.form.shipping:
    class: AppBundle\Form\ShippingType
    arguments:
    - %app.us_shipping.enabled%
    tags:
    - { type: form.type }
    ✓ Pass Global Settings To Constructor

    View Slide

  74. Bernhard Schussek · webmozart.io 78/122
    class ShippingType extends AbstractType
    {
    private $usShipping;
    public function __construct($usShipping)
    {
    $this->usShipping = (bool) $usShipping;
    }
    // ...
    }
    ✓ Pass Global Settings To Constructor

    View Slide

  75. Bernhard Schussek · webmozart.io 79/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  76. Bernhard Schussek · webmozart.io 80/122
    {{ form(form) }}
    ✓ Use form(form) for Rapid Prototyping

    View Slide

  77. Bernhard Schussek · webmozart.io 81/122
    {{ form_row(form.firstName) }}
    {{ form_row(form.lastName) }}
    {{ form_row(form.address) }}
    {{ form_row(form.submit) }}
    ✓ Use form_row() to Control Field Order

    View Slide

  78. Bernhard Schussek · webmozart.io 82/122
    {{ form_row(form.address, {
    'label': 'Your Address',
    'attr': {'class': 'shippable-address'},
    'label_attr': {'data-id': 5}
    }) }}
    ✓ Set Labels/Attributes in the View

    View Slide

  79. Bernhard Schussek · webmozart.io 83/122
    ✓ Use the Form Debugger

    View Slide

  80. Bernhard Schussek · webmozart.io 84/122

    {{ form_errors(form.paymentMethod) }}

    Payment Method
    {{ form_widget(form.paymentMethod) }}


    ✓ Write Custom HTML By Hand

    View Slide

  81. Bernhard Schussek · webmozart.io 85/122
    {% form_theme form 'my-theme.html.twig' %}
    {{ form(form) }}
    ✗ (Mostly) Don't Use Themes

    View Slide

  82. Bernhard Schussek · webmozart.io 86/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  83. Bernhard Schussek · webmozart.io 87/122
    /**
    * @Assert\NotNull
    * @Assert\GreaterThan(0)
    */
    private $cost;
    ✗ Don't Add "Assert" Alias

    View Slide

  84. Bernhard Schussek · webmozart.io 88/122
    ✓ Use PhpStorm with the Annotation Plugin

    View Slide

  85. Bernhard Schussek · webmozart.io 89/122
    /**
    * @NotNull
    * @GreaterThan(0)
    */
    private $cost;
    ✓ Auto-Import Annotations

    View Slide

  86. Bernhard Schussek · webmozart.io 90/122
    /**
    * @NotNull
    * @Expression("value < this.getThreshold()")
    */
    private $memory;
    ✓ Use the @Expression Constraint

    View Slide

  87. Bernhard Schussek · webmozart.io 91/122
    /**
    * @Callback
    */
    public function validateTotal(
    ExecutionContextInterface $context)
    {
    if ($this->total !== $this->getProductsTotal()) {
    $this->context->addViolation('Invalid.');
    }
    }
    ✓ Use the @Callback Constraint

    View Slide

  88. Bernhard Schussek · webmozart.io 92/122
    $context->addViolation('product.total.invalid');
    ✓ Use Message Keys

    View Slide

  89. Bernhard Schussek · webmozart.io 93/122
    $context->buildViolation('product.total.invalid')
    ->setParameter('{{ expected }}', $expected)
    ->addViolation();
    ✓ Use Message Parameters

    View Slide

  90. Bernhard Schussek · webmozart.io 94/122
    /**
    * @NotNull
    * @GreaterThan(0, groups="Checkout")
    */
    private $total;
    ✓ Use Validation Groups for Partial Validation

    View Slide

  91. Bernhard Schussek · webmozart.io 95/122
    public function configureOptions(OptionsResolver $resolv
    {
    $resolver->setDefaults([
    'validation_groups' => new GroupSequence([
    'Default',
    'VatCheck'
    ]),
    ]);
    }
    ✓ Use Group Sequences for Sequential Validation

    View Slide

  92. Bernhard Schussek · webmozart.io 96/122
    class ProductName extends Regex
    {
    public function __construct($options = [])
    {
    parent::__construct(array_replace([
    'pattern' => '/^\w.*$/'
    ], $options);
    }
    public function validatedBy()
    {
    return RegexValidator::class;
    }
    }
    ✓ Create Domain-Specific Constraints

    View Slide

  93. Bernhard Schussek · webmozart.io 97/122
    class ProductNameValidator extends ConstraintValidator
    {
    public function validated($value, Constraint $constr
    {
    $this->context->getValidator()
    ->inContext($this->context)
    ->validate($value, new Regex(self::PATTERN))
    ->validate($value, new NotEqualTo('keyword')
    ;
    }
    }
    ✓ Create Composite Constraints

    View Slide

  94. Bernhard Schussek · webmozart.io 98/122
    class ProductNameValidator extends ConstraintValidator
    {
    public function validated($value, Constraint $constr
    {
    if (null === $value) {
    return;
    }
    // ...
    }
    }
    ✓ Ignore Null Values

    View Slide

  95. Bernhard Schussek · webmozart.io 99/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  96. Bernhard Schussek · webmozart.io 100/122
    Clark Kent
    ...
    Submit
    Name
    VAT
    $order->getCustomerName()
    $order->setCustomerName()
    Order
    +orderNumber
    +customerName
    +vat
    1
    2
    3

    View Slide

  97. Bernhard Schussek · webmozart.io 101/122
    $builder->add('vat', TextType::class, [
    'property_path' => 'customer.vatId'
    ]);
    ✓ Decouple Forms from the Model Structure

    View Slide

  98. Bernhard Schussek · webmozart.io 102/122
    'customer.name' == $data->getCustomer()->setName()
    'items[0].amount' == $data->getItems()[0]->setAmount()
    ✓ Learn Property Paths

    View Slide

  99. Bernhard Schussek · webmozart.io 103/122
    class Customer
    {
    public function __construct($name, Address $address)
    {
    // ...
    }
    }
    What About Non-Empty Constructors?

    View Slide

  100. Bernhard Schussek · webmozart.io 104/122
    public function configureOptions(OptionsResolver $resolv
    {
    $resolver->setDefaults([
    'empty_data' => function (FormInterface $form) {
    return new Customer(
    $form->get('name')->getData(),
    $form->get('address')->getData()
    );
    },
    ]);
    }
    ✓ Use "empty_data" for Non-Empty Constructors

    View Slide

  101. Bernhard Schussek · webmozart.io 105/122
    class Money
    {
    public function __construct($amount)
    {
    $this->amount = $amount;
    }
    public function getAmount()
    {
    return $this->amount;
    }
    }
    What About Value Objects?

    View Slide

  102. Bernhard Schussek · webmozart.io 106/122
    Model Format View Format
    DateTime string
    DateTimeType

    View Slide

  103. Bernhard Schussek · webmozart.io 107/122
    Model Format View Format
    Money string
    MyMoneyType

    View Slide

  104. Bernhard Schussek · webmozart.io 108/122
    class MyMoneyType extends AbstractType
    implements DataTransformerInterface
    {
    public function buildForm(
    FormBuilderInterface $builder, array $options)
    {
    $builder->addModelTransformer($this);
    }
    public function getParent()
    {
    return MoneyType::class;
    }
    // ...
    }
    ✓ Use Data Transformers (1/3)

    View Slide

  105. Bernhard Schussek · webmozart.io 109/122
    class MyMoneyType extends AbstractType
    implements DataTransformerInterface
    {
    // ...
    public function transform($money)
    {
    if (null === $money) {
    return null;
    }
    return $money->getAmount();
    }
    // ...
    }
    ✓ Use Data Transformers (2/3)

    View Slide

  106. Bernhard Schussek · webmozart.io 110/122
    class MyMoneyType extends AbstractType
    implements DataTransformerInterface
    {
    // ...
    public function reverseTransform($amount)
    {
    if (null === $amount) {
    return null;
    }
    return new Money($amount);
    }
    }
    ✓ Use Data Transformers (3/3)

    View Slide

  107. Bernhard Schussek · webmozart.io 111/122
    class Customer
    {
    public function relocate(Address $address)
    {
    // ...
    }
    }
    What About Non-Standard Methods?

    View Slide

  108. Bernhard Schussek · webmozart.io 112/122
    Clark Kent
    ...
    Submit
    Name
    VAT
    $order->getCustomerName()
    $order->setCustomerName()
    Order
    +orderNumber
    +customerName
    +vat
    1
    2
    3

    View Slide

  109. Bernhard Schussek · webmozart.io 113/122
    class CustomerType extends AbstractType
    implements DataMapperInterface
    {
    public function buildForm(
    FormBuilderInterface $builder, array $options)
    {
    $builder->setDataMapper($this);
    }
    // ...
    }
    ✓ Use Data Mappers (1/3)

    View Slide

  110. Bernhard Schussek · webmozart.io 114/122
    class CustomerType extends AbstractType
    implements DataMapperInterface
    {
    // ...
    public function mapDataToForms($customer, $forms)
    {
    $forms = iterator_to_array($forms);
    $forms['address']->setData($customer->getAddress
    // ...
    }
    // ...
    }
    ✓ Use Data Mappers (2/3)

    View Slide

  111. Bernhard Schussek · webmozart.io 115/122
    class CustomerType extends AbstractType
    implements DataMapperInterface
    {
    // ...
    public function mapFormsToData($forms, &$customer)
    {
    $forms = iterator_to_array($forms);
    $customer->relocate($forms['address']->getData()
    // ...
    }
    // ...
    }
    ✓ Use Data Mappers (3/3)

    View Slide

  112. Bernhard Schussek · webmozart.io 116/122
    class Order
    {
    public function setCustomerNumber(int $orderNumber)
    {
    // ...
    }
    }
    What About Strict Models?

    View Slide

  113. Bernhard Schussek · webmozart.io 117/122
    public function configureOptions(OptionsResolver $resolv
    {
    $resolver->setDefaults([
    'data_class' => PlaceOrder::class,
    ]);
    }
    class PlaceOrder
    {
    public $orderNumber;
    public $customerNumber;
    // ...
    }
    ✓ Map to DTOs

    View Slide

  114. Bernhard Schussek · webmozart.io 118/122
    class PlaceOrder
    {
    /** @OrderNumber */
    public $orderNumber;
    }
    /** @Entity */
    class Order
    {
    /** @OrderNumber */
    private $orderNumber;
    }
    ✓ Use Domain-Specific Constraints

    View Slide

  115. Bernhard Schussek · webmozart.io 119/122
    public function configureOptions(OptionsResolver $resolv
    {
    $resolver->setDefaults([
    'data_class' => Order::class,
    ]);
    }
    /**
    * @Entity
    */
    class Order { ... }
    ✓ Map to Doctrine Entities in RAD Applications

    View Slide

  116. Bernhard Schussek · webmozart.io 120/122
    PlaceOrderType
    FormFactory
    Controller
    createForm() buildForm()
    Form
    handleRequest()
    Order
    Validator
    createView()
    Twig
    FormBuilder
    Form
    Order
    FormView
    FormView
    update
    validate()
    render()

    View Slide

  117. Bernhard Schussek · webmozart.io 121/122
    $builder
    ->add('country', 'entity', [ ... ])
    ->add('province', 'entity', [ ... ])
    ;
    What About Field Dependencies?

    View Slide

  118. Bernhard Schussek · webmozart.io 122/122
    $builder->get('country')->addEventListener(
    FormEvents::POST_SET_DATA,
    $addProvinceField
    );
    $builder->get('country')->addEventListener(
    FormEvents::POST_SUBMIT,
    $addProvinceField
    );
    ✓ Use POST_* Hooks On Fields (1/2)

    View Slide

  119. Bernhard Schussek · webmozart.io 123/122
    $addProvinceField = function (FormEvent $event) {
    $form = $event->getForm()->getParent();
    $country = $event->getData();
    $form->add('province', EntityType::class, [
    'class' => Province::class,
    'query_builder' => function ($repo) use ($countr
    // query provinces by $country
    },
    ]);
    };
    ✓ Use POST_* Hooks On Fields (2/2)

    View Slide

  120. Bernhard Schussek · webmozart.io 124/122
    Forms can be simple if done right!

    View Slide

  121. Bernhard Schussek · webmozart.io 125/122
    Complex things are complex...

    View Slide

  122. Bernhard Schussek · webmozart.io 126/122
    Questions?
    Questions?
    joind.in/talk/4beec
    joind.in/talk/4beec
    Bernhard Schussek
    Bernhard Schussek
    @webmozart
    @webmozart
    webmozart.io

    View Slide