Symfony2: removing an object from the middle of a collection

1. Overview

I want to remove an object from a collection using the symfony2 form.

1.1 Problem

I can add new objects to the collection and delete them if the added or deleted object is at the end of the collection . As soon as I delete one from the beginning or the middle, I get the following error:

When I try to do this, I get this error:

Neither the id property, nor any of the addId () / removeId (), setId (), id (), __set (), or __call () methods exist and have public access in the class "ApiBundle \ Entity \ Data \ Column".

1.2 Code

Here is all the relevant code.

Data

/** * Data * * @ORM\Table(name="data__data") * @ORM\Entity(repositoryClass="ApiBundle\Repository\Data\DataRepository") */ class Data { /** * @var integer * * @ORM\Column(name="id", type="string") * @ORM\Id * @ORM\GeneratedValue(strategy="UUID") */ protected $id; /** * @var ArrayCollection * @ORM\OneToMany(targetEntity="Column", mappedBy="parent", cascade={"all"}, orphanRemoval=true) */ protected $columns; /** * Initialise the array collections */ public function __construct() { $this->columns = new ArrayCollection(); } /** * @param mixed $columns */ public function setColumns($columns) { $this->columns = $columns; } /** * @param Column $column */ public function addColumn($column) { $column->setParent($this); $this->columns->add($column); } /** * @param Column $column */ public function removeColumn($column) { $this->columns->removeElement($column); } } 

Column

 /** * Data * * @ORM\Table(name="data__column") * @ORM\Entity */ class Column { /** * @var integer * * @ORM\Column(name="id", type="string") * @ORM\Id * @ORM\GeneratedValue(strategy="UUID") */ protected $id; /** * @var Data * @ORM\ManyToOne(targetEntity="Data", inversedBy="columns") */ protected $parent; /** * @return Data */ public function getParent() { return $this->parent; } /** * @param Data $parent */ public function setParent($parent) { $this->parent = $parent; } } 

DataFormType

 class DataFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('id') ->add('columns', 'collection', array( 'type' => new ColumnFormType(), 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false )) ; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'ApiBundle\Entity\Data\Data', 'csrf_protection' => false )); } public function getName() { return 'data'; } } 

ColumnFormType

 class ColumnFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('id'); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'ApiBundle\Entity\Data\Column', 'csrf_protection' => false )); } public function getName() { return 'data_column'; } } 

I removed the code from these snippets for clarity

1.3 Conclusion

As I said, I have no problem adding or removing from the end of the collection. But as soon as he is somewhere else, he is mistaken.

Thanks for any help.

+5
source share
1 answer

The error is caused by the lack of saving the collection key.

CollectionType has a high viscosity with ResizeListener . He fills the collection form with subforms:

 public function preSetData(FormEvent $event) { $form = $event->getForm(); $data = $event->getData(); ... // Then add all rows again in the correct order foreach ($data as $name => $value) { $form->add($name, $this->type, array_replace(array( 'property_path' => '['.$name.']', ), $this->options)); } } 

Thus, each subformation is mapped to a collection object (basic data) and has a name that applies to the collection index, for example. '[0]', '[1]'. When you remove items from the collection, the ResizeListener removes the redundant subforms.

 public function preSubmit(FormEvent $event) { $form = $event->getForm(); $data = $event->getData(); ... // Remove all empty rows if ($this->allowDelete) { foreach ($form as $name => $child) { if (!isset($data[$name])) { $form->remove($name); } } } } 

Suppose there was data[columns][0][id]=1, data[columns][1][id]=2, data[columns][2][id]=3 .

When you delete an item from the end, everything is fine. There comes data[columns][0][id]=1, data[columns][1][id]=2 with the corresponding content. Then the subformat [2] will be deleted, and then the element with index 2 will be removed from the collection.

When you delete an item not at the end, and you do not save the keys, an error occurs. For example, you send data[columns][0][id]=2, data[columns][1][id]=3 . ResizeListener will delete the ResizeListener with index [2] . The basic data will be redefined for the remaining subforms ( [0] , [1] ) and their child element ( id ). Most nested subforms are processed first.

  [0] (Column) [id] 1 => 2 [1] (Column) [id] 2 => 3 

Then, PropertyPathMapper find that the id subform data is not equal to the value of the Column id property (this is the base data [0] ):

 public function mapFormsToData($forms, &$data) { ... if (!is_object($data) || !$config->getByReference() || $form->getData() !== $this->propertyAccessor->getValue($data, $propertyPath)) { $this->propertyAccessor->setValue($data, $propertyPath, $form->getData()); } ... } 

This will make the PropertyAccessor to set the new id value to the Column object. The latter will throw an exception, since it is impossible to set a new id column in the columns (no settings, the property is not public, etc.).

Solution:. To keep the key order. If you get data[columns][0][id]=1, data[columns][1][id]=2, data[columns][2][id]=3 and you delete the first element, you must send data[columns][1][id]=2, data[columns][2][id]=3

PS Keeping the order of the keys for forms is good practice for all occasions. This will prevent you from redundant UPDATE queries and loops.

+2
source

Source: https://habr.com/ru/post/1207885/


All Articles