public function onBindNormData(FilterDataEvent $event) { $originalData = $event->getForm()->getNormData(); // If we are not allowed to change anything, return immediately if (!$this->allowAdd && !$this->allowDelete) { // Don't set to the snapshot as then we are switching from the // original object to its copy, which might break things $event->setData($originalData); return; } $form = $event->getForm(); $data = $event->getData(); $childPropertyPath = null; $parentData = null; $addMethod = null; $removeMethod = null; $propertyPath = null; $plural = null; if ($form->hasParent() && $form->getAttribute('property_path')) { $propertyPath = new PropertyPath($form->getAttribute('property_path')); $childPropertyPath = $propertyPath; $parentData = $form->getParent()->getClientData(); $lastElement = $propertyPath->getElement($propertyPath->getLength() - 1); // If the property path contains more than one element, the parent // data is the object at the parent property path if ($propertyPath->getLength() > 1) { $parentData = $propertyPath->getParent()->getValue($parentData); // Property path relative to $parentData $childPropertyPath = new PropertyPath($lastElement); } // The plural form is the last element of the property path $plural = ucfirst($lastElement); } if (null === $data) { $data = array(); } if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { throw new UnexpectedTypeException($data, 'array or (\\Traversable and \\ArrayAccess)'); } if (null !== $originalData && !is_array($originalData) && !($originalData instanceof \Traversable && $originalData instanceof \ArrayAccess)) { throw new UnexpectedTypeException($originalData, 'array or (\\Traversable and \\ArrayAccess)'); } // Check if the parent has matching methods to add/remove items if ($this->mergeStrategy & self::MERGE_INTO_PARENT && is_object($parentData)) { $reflClass = new \ReflectionClass($parentData); $addMethodNeeded = $this->allowAdd && !$this->addMethod; $removeMethodNeeded = $this->allowDelete && !$this->removeMethod; // Any of the two methods is required, but not yet known if ($addMethodNeeded || $removeMethodNeeded) { $singulars = (array) FormUtil::singularify($plural); foreach ($singulars as $singular) { // Try to find adder, but don't override preconfigured one if ($addMethodNeeded) { $addMethod = 'add' . $singular; // False alert if (!$this->isAccessible($reflClass, $addMethod, 1)) { $addMethod = null; } } // Try to find remover, but don't override preconfigured one if ($removeMethodNeeded) { $removeMethod = 'remove' . $singular; // False alert if (!$this->isAccessible($reflClass, $removeMethod, 1)) { $removeMethod = null; } } // Found all that we need. Abort search. if ((!$addMethodNeeded || $addMethod) && (!$removeMethodNeeded || $removeMethod)) { break; } // False alert $addMethod = null; $removeMethod = null; } } // Set preconfigured adder if ($this->allowAdd && $this->addMethod) { $addMethod = $this->addMethod; if (!$this->isAccessible($reflClass, $addMethod, 1)) { throw new FormException(sprintf('The public method "%s" could not be found on class %s', $addMethod, $reflClass->getName())); } } // Set preconfigured remover if ($this->allowDelete && $this->removeMethod) { $removeMethod = $this->removeMethod; if (!$this->isAccessible($reflClass, $removeMethod, 1)) { throw new FormException(sprintf('The public method "%s" could not be found on class %s', $removeMethod, $reflClass->getName())); } } } // Calculate delta between $data and the snapshot created in PRE_BIND $itemsToDelete = array(); $itemsToAdd = is_object($data) ? clone $data : $data; if ($this->dataSnapshot) { foreach ($this->dataSnapshot as $originalItem) { foreach ($data as $key => $item) { if ($item === $originalItem) { // Item found, next original item unset($itemsToAdd[$key]); continue 2; } } // Item not found, remember for deletion foreach ($originalData as $key => $item) { if ($item === $originalItem) { $itemsToDelete[$key] = $item; continue 2; } } } } if ($addMethod || $removeMethod) { // If methods to add and to remove exist, call them now, if allowed if ($removeMethod) { foreach ($itemsToDelete as $item) { $parentData->{$removeMethod}($item); } } if ($addMethod) { foreach ($itemsToAdd as $item) { $parentData->{$addMethod}($item); } } $event->setData($childPropertyPath->getValue($parentData)); } elseif ($this->mergeStrategy & self::MERGE_NORMAL) { if (!$originalData) { // No original data was set. Set it if allowed if ($this->allowAdd) { $originalData = $data; } } else { // Original data is an array-like structure // Add and remove items in the original variable if ($this->allowDelete) { foreach ($itemsToDelete as $key => $item) { unset($originalData[$key]); } } if ($this->allowAdd) { foreach ($itemsToAdd as $key => $item) { if (!isset($originalData[$key])) { $originalData[$key] = $item; } else { $originalData[] = $item; } } } } $event->setData($originalData); } }