Skip to content

Commit da2447e

Browse files
committed
[Form] Fixed MergeCollectionListener when used with a custom property path
1 parent b56502f commit da2447e

File tree

3 files changed

+98
-18
lines changed

3 files changed

+98
-18
lines changed

src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Form\Exception\UnexpectedTypeException;
1919
use Symfony\Component\Form\Exception\FormException;
2020
use Symfony\Component\Form\Util\FormUtil;
21+
use Symfony\Component\Form\Util\PropertyPath;
2122

2223
/**
2324
* @author Bernhard Schussek <bschussek@gmail.com>
@@ -150,10 +151,31 @@ public function onBindNormData(FilterDataEvent $event)
150151

151152
$form = $event->getForm();
152153
$data = $event->getData();
153-
$parentData = $form->hasParent() ? $form->getParent()->getClientData() : null;
154+
$childPropertyPath = null;
155+
$parentData = null;
154156
$addMethod = null;
155157
$removeMethod = null;
156-
$getMethod = null;
158+
$propertyPath = null;
159+
$plural = null;
160+
161+
if ($form->hasParent() && $form->getAttribute('property_path')) {
162+
$propertyPath = new PropertyPath($form->getAttribute('property_path'));
163+
$childPropertyPath = $propertyPath;
164+
$parentData = $form->getParent()->getClientData();
165+
$lastElement = $propertyPath->getElement($propertyPath->getLength() - 1);
166+
167+
// If the property path contains more than one element, the parent
168+
// data is the object at the parent property path
169+
if ($propertyPath->getLength() > 1) {
170+
$parentData = $propertyPath->getParent()->getValue($parentData);
171+
172+
// Property path relative to $parentData
173+
$childPropertyPath = new PropertyPath($lastElement);
174+
}
175+
176+
// The plural form is the last element of the property path
177+
$plural = ucfirst($lastElement);
178+
}
157179

158180
if (null === $data) {
159181
$data = array();
@@ -169,7 +191,6 @@ public function onBindNormData(FilterDataEvent $event)
169191

170192
// Check if the parent has matching methods to add/remove items
171193
if (($this->mergeStrategy & self::MERGE_INTO_PARENT) && is_object($parentData)) {
172-
$plural = ucfirst($form->getName());
173194
$reflClass = new \ReflectionClass($parentData);
174195
$addMethodNeeded = $this->allowAdd && !$this->addMethod;
175196
$removeMethodNeeded = $this->allowDelete && !$this->removeMethod;
@@ -235,18 +256,6 @@ public function onBindNormData(FilterDataEvent $event)
235256
));
236257
}
237258
}
238-
239-
if ($addMethod || $removeMethod) {
240-
$getMethod = 'get' . $plural;
241-
242-
if (!$this->isAccessible($reflClass, $getMethod, 0)) {
243-
throw new FormException(sprintf(
244-
'The public method "%s" could not be found on class %s',
245-
$getMethod,
246-
$reflClass->getName()
247-
));
248-
}
249-
}
250259
}
251260

252261
// Calculate delta between $data and the snapshot created in PRE_BIND
@@ -287,7 +296,7 @@ public function onBindNormData(FilterDataEvent $event)
287296
}
288297
}
289298

290-
$event->setData($parentData->$getMethod());
299+
$event->setData($childPropertyPath->getValue($parentData));
291300
} elseif ($this->mergeStrategy & self::MERGE_NORMAL) {
292301
if (!$originalData) {
293302
// No original data was set. Set it if allowed

src/Symfony/Component/Form/Util/PropertyPath.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ public function __toString()
110110
return $this->string;
111111
}
112112

113+
/**
114+
* Returns the length of the property path.
115+
*
116+
* @return integer
117+
*/
118+
public function getLength()
119+
{
120+
return $this->length;
121+
}
122+
113123
/**
114124
* Returns the parent property path.
115125
*

tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerTest.php

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ public function removeAxis($axis) {}
4949
public function getAxes() {}
5050
}
5151

52+
class MergeCollectionListenerTest_CompositeCar
53+
{
54+
public function getStructure() {}
55+
}
56+
57+
class MergeCollectionListenerTest_CarStructure
58+
{
59+
public function addAxis($axis) {}
60+
61+
public function removeAxis($axis) {}
62+
63+
public function getAxes() {}
64+
}
65+
5266
abstract class MergeCollectionListenerTest extends \PHPUnit_Framework_TestCase
5367
{
5468
private $dispatcher;
@@ -74,9 +88,11 @@ protected function getBuilder($name = 'name')
7488
return new FormBuilder($name, $this->factory, $this->dispatcher);
7589
}
7690

77-
protected function getForm($name = 'name')
91+
protected function getForm($name = 'name', $propertyPath = null)
7892
{
79-
return $this->getBuilder($name)->getForm();
93+
$propertyPath = $propertyPath ?: $name;
94+
95+
return $this->getBuilder($name)->setAttribute('property_path', $propertyPath)->getForm();
8096
}
8197

8298
protected function getMockForm()
@@ -359,6 +375,51 @@ public function testCallAdderIfAllowAdd($mode)
359375
$this->assertEquals('RESULT', $event->getData());
360376
}
361377

378+
/**
379+
* @dataProvider getModesWithMergeIntoParent
380+
*/
381+
public function testCallAdderIfCustomPropertyPath($mode)
382+
{
383+
$this->form = $this->getForm('structure_axes', 'structure.axes');
384+
385+
$parentData = $this->getMock(__CLASS__ . '_CompositeCar');
386+
$parentForm = $this->getForm('car');
387+
$parentForm->setData($parentData);
388+
$parentForm->add($this->form);
389+
390+
$modifData = $this->getMock(__CLASS__ . '_CarStructure');
391+
392+
$originalDataArray = array(1 => 'second');
393+
$originalData = $this->getData($originalDataArray);
394+
$newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third'));
395+
396+
$listener = new MergeCollectionListener(true, false, $mode);
397+
398+
$this->form->setData($originalData);
399+
400+
$event = new DataEvent($this->form, $newData);
401+
$listener->preBind($event);
402+
403+
$parentData->expects($this->once())
404+
->method('getStructure')
405+
->will($this->returnValue($modifData));
406+
407+
$modifData->expects($this->at(0))
408+
->method('addAxis')
409+
->with('first');
410+
$modifData->expects($this->at(1))
411+
->method('addAxis')
412+
->with('third');
413+
$modifData->expects($this->at(2))
414+
->method('getAxes')
415+
->will($this->returnValue('RESULT'));
416+
417+
$event = new FilterDataEvent($this->form, $newData);
418+
$listener->onBindNormData($event);
419+
420+
$this->assertEquals('RESULT', $event->getData());
421+
}
422+
362423
/**
363424
* @dataProvider getModesWithMergeIntoParent
364425
*/

0 commit comments

Comments
 (0)