Symfony day 2016

Preview:

Citation preview

Samuele Lilli - DonCallisto

Rome, 28 October 2016

A journey into Symfony form component

Samuele Lilli - DonCallisto

WHO AM I?

Samuele Lilli - DonCallisto

Samuele Lilli

DonCallisto

Samuele Lilli - DonCallisto

Samuele Lilli - DonCallisto

Samuele Lilli - DonCallisto

Backend developer @

website: www.madisoft.it

tech blog: labs.madisoft.it

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

My first talk

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

FORM COMPONENT

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

Samuele Lilli - DonCallisto

FORM COMPONENT

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

Samuele Lilli - DonCallisto

FORM COMPONENT

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

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

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

Provides twig facilities for render labels/fields/errors

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

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

Samuele Lilli - DonCallisto

SUMMARYEntityType

CollectionType

Form Data Filtering on Entity / Collection

Form Events

Form Data Types

Data Transformers

Value Objects

Property Path

Samuele Lilli - DonCallisto

ENTITY TYPE

Samuele Lilli - DonCallisto

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

entities to another

Samuele Lilli - DonCallisto

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

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

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

Samuele Lilli - DonCallisto

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

return $this; }

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

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

Samuele Lilli - DonCallisto

Products are not associated to categories

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

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

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.

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.

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

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.

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

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)

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

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

Samuele Lilli - DonCallisto

COLLECTION TYPE

Samuele Lilli - DonCallisto

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

add/remove directly elements from collection

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

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

Samuele Lilli - DonCallisto

Samuele Lilli - DonCallisto

Samuele Lilli - DonCallisto

Element not deleted!

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

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

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

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

Samuele Lilli - DonCallisto

Set orphanRemoval to true on the attribute

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

NOT RECOMMENDED

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

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

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

Samuele Lilli - DonCallisto

FORM DATA FILTERING

(for entity and collection type)

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

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

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

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

Samuele Lilli - DonCallisto

Is repository the only way to filter data?

Do I need to create a repository on purpose?

Samuele Lilli - DonCallisto

NO

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

FORM EVENTS

(overview)

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

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

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

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

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

RENDER

FORM CREATION

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

Samuele Lilli - DonCallisto

PRE_SET_DATA POST_SET_DATA

CONTROLLERVIEW

NEW FORM

BIND

FORM POSTPOST

PRE_SUBMIT SUBMIT POST_SUBMIT

IS VALID

PERSIST

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

Samuele Lilli - DonCallisto

BECAUSE OF THIS

Samuele Lilli - DonCallisto

NEVER SET PARENT DATA

FROM CHILD FORM

Samuele Lilli - DonCallisto

EVER!

Samuele Lilli - DonCallisto

DATA TYPES

Samuele Lilli - DonCallisto

DATA TYPES

MODEL DATA

NORM DATA

VIEW DATA

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

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

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.

Samuele Lilli - DonCallisto

DATA TRANSFORMERS

Samuele Lilli - DonCallisto

MODEL TRANSFORMER

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

MODEL TRANSFORMER

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Samuele Lilli - DonCallisto

ENTITY - MODEL DATA

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

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Samuele Lilli - DonCallisto

FORM FIELD

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

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

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

FORM FIELD

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Samuele Lilli - DonCallisto

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

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

Samuele Lilli - DonCallisto

ENTITY

MODEL DATA NORM DATA

NEW FORM BIND

TRANSFORM

REVERSE TRANSFORM

MODEL TRANSFORMER

Samuele Lilli - DonCallisto

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

REVERSE TRANSFORM

Samuele Lilli - DonCallisto

VIEW TRANSFORMER

Samuele Lilli - DonCallisto

VIEW

NORM DATA VIEW DATA

CREATE VIEWPOST

TRANSFORM

REVERSE TRANSFORM

VIEW TRANSFORMER

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

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)

Samuele Lilli - DonCallisto

FORM EVENTS(reprise)

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

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

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

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

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

Samuele Lilli - DonCallisto

Change data from the request

Add/Remove form fields

USED FOR

EVENT DATAREQUEST DATA (ARRAY)

PRE SUBMIT EVENT

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

Samuele Lilli - DonCallisto

Modify NormData

No real example will be showed

EVENT DATANORM DATA

SUBMIT EVENT

USED FOR

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

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

Samuele Lilli - DonCallisto

VALUE OBJECTS

Samuele Lilli - DonCallisto

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

Samuele Lilli - DonCallisto

NO SETTERS

Samuele Lilli - DonCallisto

CONSTRUCTOR

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

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

Samuele Lilli - DonCallisto

PROPERTY PATH

Samuele Lilli - DonCallisto

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

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

Samuele Lilli - DonCallisto

DON’T CREATE AN EMBEDDED FORM ON

PURPOSE

Samuele Lilli - DonCallisto

DON’T USE UNMAPPED FIELDS AND FORM EVENTS

Samuele Lilli - DonCallisto

USE PROPERTY PATH!

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

QUESTIONS?

Samuele Lilli - DonCallisto

THANK YOU