symfony day 2016

130
Samuele Lilli - DonCallisto Rome, 28 October 2016 A journey into Symfony form component

Upload: samuele-lilli

Post on 21-Mar-2017

201 views

Category:

Internet


0 download

TRANSCRIPT

Page 1: Symfony day 2016

Samuele Lilli - DonCallisto

Rome, 28 October 2016

A journey into Symfony form component

Page 2: Symfony day 2016

Samuele Lilli - DonCallisto

WHO AM I?

Page 3: Symfony day 2016

Samuele Lilli - DonCallisto

Samuele Lilli

DonCallisto

Page 4: Symfony day 2016

Samuele Lilli - DonCallisto

Page 5: Symfony day 2016

Samuele Lilli - DonCallisto

Page 6: Symfony day 2016

Samuele Lilli - DonCallisto

Backend developer @

website: www.madisoft.it

tech blog: labs.madisoft.it

Page 7: Symfony day 2016

Samuele Lilli - DonCallisto

WE ARE HIRING!(wanna join? ask us at the end of the talk or visit our website)

Page 8: Symfony day 2016

Samuele Lilli - DonCallisto

My first talk

Page 9: Symfony day 2016

Samuele Lilli - DonCallisto

https://joind.in/talk/5a6bd

Page 10: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENT

(http://www.freepik.com/free-photos-vectors/smile - Smile vector designed by Freepik)

Page 11: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENT

(http://www.freepik.com/free-photos-vectors/smile - Smile vector designed by Freepik)

Page 12: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENT

(http://www.freepik.com/free-photos-vectors/smile - Smile vector designed by Freepik)

Page 13: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENTStandalone component (install it via composer/packagist or github)

Page 14: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENTStandalone component (install it via composer/packagist or github)

Provides twig facilities for render labels/fields/errors

Page 15: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENTStandalone 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, …)

Page 16: Symfony day 2016

Samuele Lilli - DonCallisto

FORM COMPONENTStandalone 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

Page 17: Symfony day 2016

Samuele Lilli - DonCallisto

SUMMARYEntityType

CollectionType

Form Data Filtering on Entity / Collection

Form Events

Form Data Types

Data Transformers

Value Objects

Property Path

Page 18: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY TYPE

Page 19: Symfony day 2016

Samuele Lilli - DonCallisto

Use EntityType when you want list or “associate” one or more

entities to another

Page 20: Symfony day 2016

Samuele Lilli - DonCallisto

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

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

Page 21: Symfony day 2016

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

Page 22: Symfony day 2016

Samuele Lilli - DonCallisto

class Category{ // …. public function addProduct(Product $product) { $this->products[] = $product;

return $this; }

public function removeProduct(Product $product) { $this->products->removeElement($product); }

Page 23: Symfony day 2016

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, ]); } //….

Page 24: Symfony day 2016

Samuele Lilli - DonCallisto

Products are not associated to categories

Page 25: Symfony day 2016

Samuele Lilli - DonCallisto

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

Page 26: Symfony day 2016

Samuele Lilli - DonCallisto

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

Page 27: Symfony day 2016

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.

Page 28: Symfony day 2016

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.

Page 29: Symfony day 2016

Samuele Lilli - DonCallisto

Data consistencyClass 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); } }

Page 30: Symfony day 2016

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.

Page 31: Symfony day 2016

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

Page 32: Symfony day 2016

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)

Page 33: Symfony day 2016

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

Page 34: Symfony day 2016

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

Page 35: Symfony day 2016

Samuele Lilli - DonCallisto

COLLECTION TYPE

Page 36: Symfony day 2016

Samuele Lilli - DonCallisto

Use CollectionType when you want to embed a collection of forms or

add/remove directly elements from collection

Page 37: Symfony day 2016

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

Page 38: Symfony day 2016

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

Page 39: Symfony day 2016

Samuele Lilli - DonCallisto

Page 40: Symfony day 2016

Samuele Lilli - DonCallisto

Page 41: Symfony day 2016

Samuele Lilli - DonCallisto

Element not deleted!

Page 42: Symfony day 2016

In the court of the crimson king - king crimson (1969)

Page 43: Symfony day 2016

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

Page 44: Symfony day 2016

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

Page 45: Symfony day 2016

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

Page 46: Symfony day 2016

Samuele Lilli - DonCallisto

Set orphanRemoval to true on the attribute

/** * @ORM\OneToMany(targetEntity="Ticket", mappedBy="user", cascade={"persist"}, orphanRemoval=true) */ protected $tickets;

NOT RECOMMENDED

Page 47: Symfony day 2016

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

Page 48: Symfony day 2016

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

Page 49: Symfony day 2016

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

Page 50: Symfony day 2016

Samuele Lilli - DonCallisto

FORM DATA FILTERING

(for entity and collection type)

Page 51: Symfony day 2016

Samuele Lilli - DonCallisto

EntityTypeUSE built-in queryBuilder option

public function buildForm(FormBuilderInterface $builder, array $options){ $builder ->add(‘foo’, EntityType::class, [ // …. ‘queryBuilder’ => function (FooRepo $fooRepo) { return $fooRepo->filterFunction(); } ]);}

Page 52: Symfony day 2016

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 optionUsually, options are used for what you cannot inject into service

Page 53: Symfony day 2016

Samuele Lilli - DonCallisto

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

Page 54: Symfony day 2016

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

Page 55: Symfony day 2016

Samuele Lilli - DonCallisto

Is repository the only way to filter data?

Do I need to create a repository on purpose?

Page 56: Symfony day 2016

Samuele Lilli - DonCallisto

NO

Page 57: Symfony day 2016

Samuele Lilli - DonCallisto

What if I need to access entity getters for filter operations?

Page 58: Symfony day 2016

Samuele Lilli - DonCallisto

FORM EVENTS

(overview)

Page 59: Symfony day 2016

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

Page 60: Symfony day 2016

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

Page 61: Symfony day 2016

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

Page 62: Symfony day 2016

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

Page 63: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Page 64: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Page 65: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Page 66: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Page 67: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Page 68: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Page 69: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 70: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 71: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 72: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 73: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 74: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 75: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 76: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 77: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 78: Symfony day 2016

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Page 79: Symfony day 2016

Samuele Lilli - DonCallisto

TIPEvery 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

Page 80: Symfony day 2016

Samuele Lilli - DonCallisto

BECAUSE OF THIS

Page 81: Symfony day 2016

Samuele Lilli - DonCallisto

NEVER SET PARENT DATA

FROM CHILD FORM

Page 82: Symfony day 2016

Samuele Lilli - DonCallisto

EVER!

Page 83: Symfony day 2016

Samuele Lilli - DonCallisto

DATA TYPES

Page 84: Symfony day 2016

Samuele Lilli - DonCallisto

DATA TYPES

MODEL DATA

NORM DATA

VIEW DATA

Page 85: Symfony day 2016

Samuele Lilli - DonCallisto

MODEL DATAMain 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!)

Page 86: Symfony day 2016

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

Page 87: Symfony day 2016

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.

Page 88: Symfony day 2016

Samuele Lilli - DonCallisto

DATA TRANSFORMERS

Page 89: Symfony day 2016

Samuele Lilli - DonCallisto

MODEL TRANSFORMER

Page 90: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

MODEL TRANSFORMER

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

Page 91: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Page 92: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY - MODEL DATA

/** * @ORM\Column(type="array", nullable=true) */protected $tags;

Page 93: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Page 94: Symfony day 2016

Samuele Lilli - DonCallisto

FORM FIELD

$builder->add('tags', TextType::class)

Page 95: Symfony day 2016

Samuele Lilli - DonCallisto

Since model data (array) is different from norm data (text) we need a model transformer

Page 96: Symfony day 2016

Samuele Lilli - DonCallisto

$builder->add('tags', TextType::class);$builder->get(‘tags’)->addModelTransformer(...);

FORM FIELD

Page 97: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Page 98: Symfony day 2016

Samuele Lilli - DonCallisto

TRANSFORMpublic function transform($tagsArray){ // transform the array to string if (null === $tagsArray) { return ''; }

return implode(',', $tagsArray);}

Page 99: Symfony day 2016

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Page 100: Symfony day 2016

Samuele Lilli - DonCallisto

public function reverseTransform($tagsString){ // transform the string back to an array if (!$tagsString) { return []; } return explode(',', $tagsString);}

REVERSE TRANSFORM

Page 101: Symfony day 2016

Samuele Lilli - DonCallisto

VIEW TRANSFORMER

Page 102: Symfony day 2016

Samuele Lilli - DonCallisto

VIEW

NORM DATA VIEW DATA

CREATE VIEWPOST

TRANSFORM

REVERSE TRANSFORM

VIEW TRANSFORMER

Page 103: Symfony day 2016

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

Page 104: Symfony day 2016

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)

Page 105: Symfony day 2016

Samuele Lilli - DonCallisto

FORM EVENTS(reprise)

Page 106: Symfony day 2016

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 DATAMODEL DATA

Page 107: Symfony day 2016

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

Page 108: Symfony day 2016

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

Page 109: Symfony day 2016

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 DATAMODEL DATA

POST SET DATA EVENT

Page 110: Symfony day 2016

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

Page 111: Symfony day 2016

Samuele Lilli - DonCallisto

Change data from the request

Add/Remove form fields

USED FOR

EVENT DATAREQUEST DATA (ARRAY)

PRE SUBMIT EVENT

Page 112: Symfony day 2016

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

Page 113: Symfony day 2016

Samuele Lilli - DonCallisto

Modify NormData

No real example will be showed

EVENT DATANORM DATA

SUBMIT EVENT

USED FOR

Page 114: Symfony day 2016

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 DATAVIEW DATA

POST SUBMIT EVENT

Page 115: Symfony day 2016

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

Page 116: Symfony day 2016

Samuele Lilli - DonCallisto

VALUE OBJECTS

Page 117: Symfony day 2016

Samuele Lilli - DonCallisto

Two object are equal if their all fields are equal, not necessary if they are the same object

Page 118: Symfony day 2016

Samuele Lilli - DonCallisto

NO SETTERS

Page 119: Symfony day 2016

Samuele Lilli - DonCallisto

CONSTRUCTOR

Page 120: Symfony day 2016

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(){ … }}

Page 121: Symfony day 2016

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

Page 122: Symfony day 2016

Samuele Lilli - DonCallisto

PROPERTY PATH

Page 123: Symfony day 2016

Samuele Lilli - DonCallisto

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

Page 124: Symfony day 2016

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

Page 125: Symfony day 2016

Samuele Lilli - DonCallisto

DON’T CREATE AN EMBEDDED FORM ON

PURPOSE

Page 126: Symfony day 2016

Samuele Lilli - DonCallisto

DON’T USE UNMAPPED FIELDS AND FORM EVENTS

Page 127: Symfony day 2016

Samuele Lilli - DonCallisto

USE PROPERTY PATH!

Page 128: Symfony day 2016

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’]); }}

Page 129: Symfony day 2016

QUESTIONS?

Page 130: Symfony day 2016

Samuele Lilli - DonCallisto

THANK YOU