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.