A journey into Symfony form component

6ec359ca87eda89de3251951372a2e8d?s=47 Samuele
October 28, 2016

A journey into Symfony form component

Presentation from italian Symfony day 2016, Rome.
Quick examples to help us understand form behavior.

6ec359ca87eda89de3251951372a2e8d?s=128

Samuele

October 28, 2016
Tweet

Transcript

  1. Samuele Lilli - DonCallisto Rome, 28 October 2016 A journey

    into Symfony form component
  2. Samuele Lilli - DonCallisto WHO AM I?

  3. Samuele Lilli - DonCallisto Samuele Lilli DonCallisto

  4. Samuele Lilli - DonCallisto

  5. Samuele Lilli - DonCallisto

  6. Samuele Lilli - DonCallisto Backend developer @ website: www.madisoft.it tech

    blog: labs.madisoft.it
  7. Samuele Lilli - DonCallisto WE ARE HIRING! (wanna join? ask

    us at the end of the talk or visit our website)
  8. Samuele Lilli - DonCallisto My first talk

  9. Samuele Lilli - DonCallisto https://joind.in/talk/5a6bd

  10. Samuele Lilli - DonCallisto FORM COMPONENT (http://www.freepik.com/free-photos-vectors/smile - Smile vector

    designed by Freepik)
  11. Samuele Lilli - DonCallisto FORM COMPONENT (http://www.freepik.com/free-photos-vectors/smile - Smile vector

    designed by Freepik)
  12. Samuele Lilli - DonCallisto FORM COMPONENT (http://www.freepik.com/free-photos-vectors/smile - Smile vector

    designed by Freepik)
  13. Samuele Lilli - DonCallisto FORM COMPONENT Standalone component (install it

    via composer/packagist or github)
  14. Samuele Lilli - DonCallisto FORM COMPONENT Standalone component (install it

    via composer/packagist or github) Provides twig facilities for render labels/fields/errors
  15. Samuele Lilli - DonCallisto FORM COMPONENT Standalone component (install it

    via composer/packagist or github) Provides twig facilities for render labels/fields/errors Handling for you data submission (bind to entity if any, validation, data transformations, …)
  16. Samuele Lilli - DonCallisto FORM COMPONENT Standalone component (install it

    via composer/packagist or github) Provides twig facilities for render labels/fields/errors Handling for you data submission (bind to entity if any, validation, data transformations, …) Provides a bunch of built-in types
  17. Samuele Lilli - DonCallisto SUMMARY EntityType CollectionType Form Data Filtering

    on Entity / Collection Form Events Form Data Types Data Transformers Value Objects Property Path
  18. Samuele Lilli - DonCallisto ENTITY TYPE

  19. Samuele Lilli - DonCallisto Use EntityType when you want list

    or “associate” one or more entities to another
  20. Samuele Lilli - DonCallisto class Product { // …. /**

    * @ORM\ManyToMany(targetEntity="Category",inversedBy="products") */ protected $categories; class Category { // …. /** *@ORM\ManyToMany(targetEntity="Product",mappedBy="categories") */ protected $products;
  21. Samuele Lilli - DonCallisto class Product { // …. /**

    * @ORM\ManyToMany(targetEntity="Category",inversedBy="products") */ protected $categories; class Category { // …. /** *@ORM\ManyToMany(targetEntity="Product",mappedBy="categories") */ protected $products; OWNING SIDE INVERSED SIDE
  22. Samuele Lilli - DonCallisto class Category { // …. public

    function addProduct(Product $product) { $this->products[] = $product; return $this; } public function removeProduct(Product $product) { $this->products->removeElement($product); }
  23. Samuele Lilli - DonCallisto class CategoryType extends AbstractType { public

    function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', TextType::class) ->add('products', EntityType::class, [ 'class' => Product::class, 'multiple' => true, 'required' => false, ]); } //….
  24. Samuele Lilli - DonCallisto Products are not associated to categories

  25. Samuele Lilli - DonCallisto http://stackoverflow.com/questions/9102063/symfony2-doctrine2-many-to-many-form-not-saving-entities

  26. Samuele Lilli - DonCallisto http://stackoverflow.com/questions/9102063/symfony2-doctrine2-many-to-many-form-not-saving-entities

  27. Samuele Lilli - DonCallisto That should not be the right

    answer Never bend your needs to software limits (unless strictly necessary) Doctrine looks only for changes ONLY on the owning side of association, so those adds will not take place. Take care yourself for data consistency (during objects lifecycle, ensure that all references are setted well in both sides). This concept is not ORM-related. We didn’t add by_reference => false into into FormType.
  28. Samuele Lilli - DonCallisto That should not be the right

    answer Never bend your needs to software limits (unless strictly necessary) Doctrine looks only for changes ONLY on the owning side of association, so those adds will not take place. Take care yourself for data consistency (during objects lifecycle, ensure that all references are setted well in both sides). This concept is not ORM-related. We didn’t add by_reference => false into into FormType.
  29. Samuele Lilli - DonCallisto Data consistency Class Product { //

    …. public function addCategory(Category $category) { if (!$this->categories->contains($category)) { $this->categories[] = $category; $category->addProduct($this); } return $this; } public function removeCategory(Category $category) { if ($this->categories->contains($category)) { $this->categories->removeElement($category); $category->removeProduct($this); } } Class Category { // …. public function addProduct(Product $product) { if (!$this->products->contains($product)) { $this->products[] = $product; $product->addCategory($this); } return $this; } public function removeProduct(Product $product) { if ($this->products->contains($product)) { $this->products->removeElement($product); $product->removeCategory($this); } }
  30. Samuele Lilli - DonCallisto This should not be the right

    answer Never bend your needs to software limits (unless strictly necessary) Doctrine looks only for changes ONLY on the owning side of association, so those adds will not take place. √ Take care yourself for data consistency (during objects lifecycle, ensure that all references are setted well in both sides). This concept is not ORM-related. √ We didn’t add by_reference => false into into FormType.
  31. Samuele Lilli - DonCallisto public function buildForm(FormBuilderInterface $builder, array $options)

    { $builder ->add('name', TextType::class) ->add('products', EntityType::class, [ 'class' => Product::class, 'multiple' => true, 'required' => false, 'by_reference' => false, ]); } by_refence => false
  32. Samuele Lilli - DonCallisto WHY BY_REFERENCE => FALSE It forces

    setter (adder) to be called on the parent element As a rule of thumb, set ALWAYS by_reference to false when dealing with objects (ArrayCollection included)
  33. Samuele Lilli - DonCallisto BY_REFERENCE => TRUE Name Cat. Name

    Name Name $cat->getProducts()->get{0}->setName(‘bar’); $cat->getProducts()->get{1}->setName(‘foobar’); $product3 = new Product(); $product3->setName(); $cat->getProducts()->add($product3); $cat->setName(‘foo’);
  34. Samuele Lilli - DonCallisto BY_REFERENCE => FALSE Name Cat. Name

    Name Name $cat->getProducts()->get{0}->setName(‘bar’); $cat->getProducts()->get{1}->setName(‘foobar’); $product3 = new Product(); $product3>setName(); $cat->addProduct($product3); $cat->setName(‘foo’);
  35. Samuele Lilli - DonCallisto COLLECTION TYPE

  36. Samuele Lilli - DonCallisto Use CollectionType when you want to

    embed a collection of forms or add/remove directly elements from collection
  37. Samuele Lilli - DonCallisto Class User { /** * @ORM\OneToMany(targetEntity="Ticket",

    mappedBy="user", cascade={"persist"}) */ protected $tickets; public function addTickets(Ticket $ticket) { if (!$this->tickets->contains($ticket)) { $this->tickets[] = $ticket; $ticket->setUser($this); } return $this; } public function removeTicket(Ticket $ticket) { $this->tickets->removeElement($ticket); }
  38. Samuele Lilli - DonCallisto $builder ->add('username', TextType::class) ->add(tickets, CollectionType::class, [

    'entry_type' => TicketType::class, 'allow_add' => true, 'allow_delete' => true, 'prototype' => true, 'by_reference' => false, ]); UserType
  39. Samuele Lilli - DonCallisto

  40. Samuele Lilli - DonCallisto

  41. Samuele Lilli - DonCallisto Element not deleted!

  42. In the court of the crimson king - king crimson

    (1969)
  43. Samuele Lilli - DonCallisto Two possible solutions “Manually” (programmatically) remove

    elements Set orphanRemoval to true on the attribute If the relationship was ManyToMany and User was the owning side, no troubles
  44. Samuele Lilli - DonCallisto “Manually” (programmatically) remove elements $originalTickets =

    new ArrayCollection(); foreach ($user->getTickets() as $ticket) { $originalTickets->add($ticket); } if ($this->isFormSubmittedAndValid($form, $request)) { foreach ($originalTickets as $originalTicket) { if (!$user->getTickets()->contains($originalTicket)) { $em->remove($originalTicket); } } }
  45. Samuele Lilli - DonCallisto “Manually” (programmatically) remove elements $originalTickets =

    new ArrayCollection(); foreach ($user->getTickets() as $ticket) { $originalTickets->add($ticket); } if ($this->isFormSubmittedAndValid($form, $request)) { foreach ($originalTickets as $originalTicket) { if (!$user->getTickets()->contains($originalTicket)) { $em->remove($originalTicket); } } }
  46. Samuele Lilli - DonCallisto Set orphanRemoval to true on the

    attribute /** * @ORM\OneToMany(targetEntity="Ticket", mappedBy="user", cascade={"persist"}, orphanRemoval=true) */ protected $tickets; NOT RECOMMENDED
  47. Samuele Lilli - DonCallisto Set orphanRemoval to true on the

    attribute http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
  48. Samuele Lilli - DonCallisto Set orphanRemoval to true on the

    attribute public function passTicketsAction(User $yelding, User $beneficiary) { foreach ($yelding->getTickets() as $ticket) { $yelding->removeTicket($ticket); $beneficiary->addTicket($ticket); } }
  49. Samuele Lilli - DonCallisto Set orphanRemoval to true on the

    attribute public function passTicketsAction(User $yelding, User $beneficiary) { foreach ($yelding->getTickets() as $ticket) { $beneficiary->addTicket($ticket); // OR → $ticket->setUser($beneficiary); } }
  50. Samuele Lilli - DonCallisto FORM DATA FILTERING (for entity and

    collection type)
  51. Samuele Lilli - DonCallisto EntityType USE built-in queryBuilder option public

    function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add(‘foo’, EntityType::class, [ // …. ‘queryBuilder’ => function (FooRepo $fooRepo) { return $fooRepo->filterFunction(); } ]); }
  52. Samuele Lilli - DonCallisto CollectionType CollectionType does not have any

    queryBuilder option Declare form as a service an inject repository (entity manager) This is the preferred way if you need the repo Pass repository as an option Usually, options are used for what you cannot inject into service
  53. Samuele Lilli - DonCallisto EntityManager injection public function __construct(EntityManager $em)

    { $this->fooRepo = $em->getRepository(Foo::class); } public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add(‘foo’, CollectionType::class, [ // …. 'data' => $this->fooRepo->filterFunction() ]); }
  54. Samuele Lilli - DonCallisto EntityManager as an option public function

    buildForm(FormBuilderInterface $builder, array $options) { $fooRepo = $options[‘fooRepo’]; $builder->add(‘foo’, CollectionType::class, [ // …. 'data' => $fooRepo>filterFunction() ]); }
  55. Samuele Lilli - DonCallisto Is repository the only way to

    filter data? Do I need to create a repository on purpose?
  56. Samuele Lilli - DonCallisto NO

  57. Samuele Lilli - DonCallisto What if I need to access

    entity getters for filter operations?
  58. Samuele Lilli - DonCallisto FORM EVENTS (overview)

  59. Samuele Lilli - DonCallisto ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $e) { /**

    @var $data User */ $data = $e->getData(); $form = $e->getForm(); $today = new \DateTime(); $criteria = Criteria::create() ->where(Criteria::expr()->gte('date', $today)); $form->add('tickets', CollectionType::class, [ // …. 'data' => $data->getTickets()->matching($criteria) ]); }); }); FORM EVENTS
  60. Samuele Lilli - DonCallisto /** * @ORM\OneToMany(targetEntity="Ticket", mappedBy="user", cascade={"persist"}, orphanRemoval=true)

    */ protected $tickets; • All tickets (entities) filtered out in the event will be removed! • Remove orphanRemoval option from the attribute and handle collection yourself FORM EVENTS
  61. Samuele Lilli - DonCallisto FORM EVENTS $form = $this->createForm(UserType::class, $user);

    $originalTickets = new ArrayCollection(); foreach ($form->get('tickets')->getData() as $ticket) { $originalTickets->add($ticket); } if ($this->isFormSubmittedAndValid($form, $request)) { foreach ($originalTickets as $originalTicket) { if ($user->getTickets()->contains($originalTicket)) { continue; } $em->remove($originalTicket); }
  62. Samuele Lilli - DonCallisto FORM EVENTS $form = $this->createForm(UserType::class, $user);

    $originalTickets = new ArrayCollection(); foreach ($form->get('tickets')->getData() as $ticket) { $originalTickets->add($ticket); } if ($this->isFormSubmittedAndValid($form, $request)) { foreach ($originalTickets as $originalTicket) { if ($user->getTickets()->contains($originalTicket)) { continue; } $em->remove($originalTicket); }
  63. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    RENDER FORM CREATION
  64. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    RENDER FORM CREATION
  65. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    RENDER FORM CREATION
  66. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    RENDER FORM CREATION
  67. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    RENDER FORM CREATION
  68. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    RENDER FORM CREATION
  69. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  70. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  71. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  72. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  73. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  74. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  75. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  76. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  77. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  78. Samuele Lilli - DonCallisto PRE_SET_DATA POST_SET_DATA CONTROLLER VIEW NEW FORM

    BIND FORM POST POST PRE_SUBMIT SUBMIT POST_SUBMIT IS VALID PERSIST
  79. Samuele Lilli - DonCallisto TIP Every group of events it’s

    called from START to END on every FORM. This means that if you have a chain of embedded form, all events are called starting from innermost forms, going up to parent form, ending on top form
  80. Samuele Lilli - DonCallisto BECAUSE OF THIS

  81. Samuele Lilli - DonCallisto NEVER SET PARENT DATA FROM CHILD

    FORM
  82. Samuele Lilli - DonCallisto EVER!

  83. Samuele Lilli - DonCallisto DATA TYPES

  84. Samuele Lilli - DonCallisto DATA TYPES MODEL DATA NORM DATA

    VIEW DATA
  85. Samuele Lilli - DonCallisto MODEL DATA Main data type of

    PRE_SET_DATA and POST_SET_DATA Reppresent data of underlying object. In previous example with product form, product field model data is a Product object. If field type is the same of underlying data, NORM DATA will be the same of MODEL DATA If field type is not the same of underlying data, you must use ModelTransformer to transform MODEL DATA into NORM DATA and vice versa (Don’t worry, we will talk about transformers next!)
  86. Samuele Lilli - DonCallisto NORM DATA Main data type of

    SUBMIT event Reppresent data after normalization. Commonly not used directly. If on MODEL DATA is not present any ModelTransform, this is the same of MODEL DATA and so the same of underlying object. If NORM DATA isn’t the same of view reppresentation, you must use ViewTransformer to transform NORM DATA into VIEW DATA and vice versa
  87. Samuele Lilli - DonCallisto VIEW DATA Main data type of

    POST_SUBMIT event Reppresent data presented to the View. It’s the data type that you get when you post the form. If on VIEW DATA is not present any ViewTransformer, this is the same of NORM DATA.
  88. Samuele Lilli - DonCallisto DATA TRANSFORMERS

  89. Samuele Lilli - DonCallisto MODEL TRANSFORMER

  90. Samuele Lilli - DonCallisto ENTITY MODEL DATA NORM DATA MODEL

    TRANSFORMER NEW FORM BIND TRANSFORM REVERSE TRANSFORM
  91. Samuele Lilli - DonCallisto ENTITY MODEL DATA NORM DATA NEW

    FORM BIND TRANSFORM REVERSE TRANSFORM MODEL TRANSFORMER
  92. Samuele Lilli - DonCallisto ENTITY - MODEL DATA /** *

    @ORM\Column(type="array", nullable=true) */ protected $tags;
  93. Samuele Lilli - DonCallisto ENTITY MODEL DATA NORM DATA NEW

    FORM BIND TRANSFORM REVERSE TRANSFORM MODEL TRANSFORMER
  94. Samuele Lilli - DonCallisto FORM FIELD $builder->add('tags', TextType::class)

  95. Samuele Lilli - DonCallisto Since model data (array) is different

    from norm data (text) we need a model transformer
  96. Samuele Lilli - DonCallisto $builder->add('tags', TextType::class); $builder->get(‘tags’)->addModelTransformer(...); FORM FIELD

  97. Samuele Lilli - DonCallisto ENTITY MODEL DATA NORM DATA NEW

    FORM BIND TRANSFORM REVERSE TRANSFORM MODEL TRANSFORMER
  98. Samuele Lilli - DonCallisto TRANSFORM public function transform($tagsArray) { //

    transform the array to string if (null === $tagsArray) { return ''; } return implode(',', $tagsArray); }
  99. Samuele Lilli - DonCallisto ENTITY MODEL DATA NORM DATA NEW

    FORM BIND TRANSFORM REVERSE TRANSFORM MODEL TRANSFORMER
  100. Samuele Lilli - DonCallisto public function reverseTransform($tagsString) { // transform

    the string back to an array if (!$tagsString) { return []; } return explode(',', $tagsString); } REVERSE TRANSFORM
  101. Samuele Lilli - DonCallisto VIEW TRANSFORMER

  102. Samuele Lilli - DonCallisto VIEW NORM DATA VIEW DATA CREATE

    VIEW POST TRANSFORM REVERSE TRANSFORM VIEW TRANSFORMER
  103. Samuele Lilli - DonCallisto DATE TYPE (TRANSFORM) // Transforms a

    normalized date into a localized date string/array) public function transform($dateTime) { if (null === $dateTime) { return ''; } if (!$dateTime instanceof \DateTimeInterface) { throw new TransformationFailedException('Expected a \DateTimeInterface.'); } $value = $this->getIntlDateFormatter()->format($dateTime->getTimestamp()); if (intl_get_error_code() != 0) { throw new TransformationFailedException(intl_get_error_message()); } return $value; }
  104. Samuele Lilli - DonCallisto // Transforms a localized date string/array

    into a normalized date. public function reverseTransform($value) { if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } if ('' === $value) { return; } $timestamp = $this->getIntlDateFormatter()->parse($value); // …. try { $dateTime = new \DateTime(sprintf('@%s', $timestamp)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } // …. return $dateTime; } DATE TYPE ( REVERSE TRANSFORM)
  105. Samuele Lilli - DonCallisto FORM EVENTS (reprise)

  106. Samuele Lilli - DonCallisto PRE SET DATA EVENT Modify data

    given during pre-population. Don’t modify form data directly but modify event data instead. Add/Remove form fields USED FOR EVENT DATA MODEL DATA
  107. Samuele Lilli - DonCallisto ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $e) { /**

    @var $data User */ $data = $e->getData(); // ← MODEL DATA $form = $e->getForm(); $today = new \DateTime(); $criteria = Criteria::create() ->where(Criteria::expr()->gte('date', $today)); $form->add(‘tickets’, CollectionType::class, [ // …. 'data' => $data->getTickets()->matching($criteria) ]); }); }); PRE_SET_DATA
  108. Samuele Lilli - DonCallisto ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $e) { if

    ($e->getData()->canHandleMail()) { // ← MODEL DATA $e->getForm()->add(‘email’, EmailType::class); } }); }); PRE_SET_DATA
  109. Samuele Lilli - DonCallisto Read pre-populated form data Don’t remove

    fields that you’ve setted “statically” on form building process. Use PRE_SET_DATA and implement the logic about fields. One exception: you are extending from a parent form where you cannot control yourself the logic. USED FOR EVENT DATA MODEL DATA POST SET DATA EVENT
  110. Samuele Lilli - DonCallisto class FooType extends BarType { //

    …. // email field added in BarType ->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $e) { if (!$e->getData()->canHandleMail()) { // ← MODEL DATA $e->getForm()->remove(‘email’); } }); }); POST_SET_DATA
  111. Samuele Lilli - DonCallisto Change data from the request Add/Remove

    form fields USED FOR EVENT DATA REQUEST DATA (ARRAY) PRE SUBMIT EVENT
  112. Samuele Lilli - DonCallisto private $canModifyEmail = true; ->addEventListener(FormEvents::PRE_SET_DATA, function

    (FormEvent $e) { $this->canModifyEmail = $e->getData()->canModifyEmail(); $e->getForm()->add(‘email, EmailType::class, [ // …. ‘attr’ => [ ‘disabled’ => !$this->canModifyEmail ], ]); }) ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $e) { if (!$this->canModifyEmail) { $e->getForm()->remove(‘email’); } }); }); PRE_SUBMIT
  113. Samuele Lilli - DonCallisto Modify NormData No real example will

    be showed EVENT DATA NORM DATA SUBMIT EVENT USED FOR
  114. Samuele Lilli - DonCallisto Fetch data after denormalization Even if

    faster to read “final” data here, don’t implement any business logic → hard to test and break SRP. If you need to modify model data, do it elsewhere just after isValid call. USED FOR EVENT DATA VIEW DATA POST SUBMIT EVENT
  115. Samuele Lilli - DonCallisto POST SUBMIT $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event)

    use ($handler) { $delete = $event->getForm()->has('delete') ? $event->getForm()->get('delete')->getData() : false; $entity = $event->getForm()->getParent()->getData(); if (!$delete) { return; } $handler->remove($entity, $event->getForm()->getName()); }); Vich\uploader-bundle
  116. Samuele Lilli - DonCallisto VALUE OBJECTS

  117. Samuele Lilli - DonCallisto Two object are equal if their

    all fields are equal, not necessary if they are the same object
  118. Samuele Lilli - DonCallisto NO SETTERS

  119. Samuele Lilli - DonCallisto CONSTRUCTOR

  120. Samuele Lilli - DonCallisto Class FooBar { private $foo; private

    $bar; public function __construct($foo, $bar) { $this->foo = $foo; $this->bar = $bar; } public function getFoo(){ … } public function getBar(){ … } }
  121. Samuele Lilli - DonCallisto Class FooBarType extends AbstractType { public

    function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add(‘foo’) ->add(‘bar’); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ ‘empty_data’ => function (FormInterface $interface) { return new FooBar($interface->get(‘foo’)->getData(), $interface->get(‘bar’)->getData()); }, ]); } }
  122. Samuele Lilli - DonCallisto PROPERTY PATH

  123. Samuele Lilli - DonCallisto Class User { //…. /* *

    @ORM\OneToOne(targetEntity=”Address”, inversedBy=”user”) */ protected $address; //…. }
  124. Samuele Lilli - DonCallisto Class Address { //…. /** *

    @ORM\OneToOne(targetEntity=”User”, mappedBy=”address”) */ protected $user; /** * @ORM\Column(type=”string”) */ protected $street; /** * @ORM\Column(type=”string”) */ protected $number; }
  125. Samuele Lilli - DonCallisto DON’T CREATE AN EMBEDDED FORM ON

    PURPOSE
  126. Samuele Lilli - DonCallisto DON’T USE UNMAPPED FIELDS AND FORM

    EVENTS
  127. Samuele Lilli - DonCallisto USE PROPERTY PATH!

  128. Samuele Lilli - DonCallisto Class UserType extends AbstractType { public

    function buildForm(FormBuilderInterface $builder, array $options) { // …. $builder ->add(‘street’, TextType::class, [‘property_path’ => ‘address.street’]) ->add(‘number’, TextType::class, [‘property_path’ => ‘address.number’]); } }
  129. QUESTIONS?

  130. Samuele Lilli - DonCallisto THANK YOU