symfony day 2016
TRANSCRIPT
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