merged branch bschussek/issue4385 (PR #4386)

Commits
-------

bad6d04 [Form] Added accessor FormConfigInterface::getByReference() and let Form clone objects if not by reference
fc23701 [Form] Correctly highlighted BC breaks in the CHANGELOG
d1864c7 [Form] Fixed: Virtual forms are ignored when prepopulating a form

Discussion
----------

[Form] Fixed: PropertyPathMapper did not always ignore virtual forms

Bug fix: yes
Feature addition: no
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: #4385
Todo: -

---------------------------------------------------------------------------

by travisbot at 2012-05-23T12:13:49Z

This pull request [fails](http://travis-ci.org/symfony/symfony/builds/1410299) (merged a7f90944 into e0238071).

---------------------------------------------------------------------------

by travisbot at 2012-05-23T12:27:30Z

This pull request [fails](http://travis-ci.org/symfony/symfony/builds/1410430) (merged 52510fee into e0238071).

---------------------------------------------------------------------------

by travisbot at 2012-05-23T12:37:00Z

This pull request [fails](http://travis-ci.org/symfony/symfony/builds/1410485) (merged ca5aee9c into e0238071).

---------------------------------------------------------------------------

by travisbot at 2012-05-23T13:01:10Z

This pull request [fails](http://travis-ci.org/symfony/symfony/builds/1410669) (merged bad6d040 into e0238071).
This commit is contained in:
Fabien Potencier 2012-05-23 21:13:17 +02:00
commit b07fb3c459
10 changed files with 282 additions and 132 deletions

View File

@ -71,13 +71,13 @@ CHANGELOG
* labels don't display field attributes anymore. Label attributes can be * labels don't display field attributes anymore. Label attributes can be
passed in the "label_attr" option/variable passed in the "label_attr" option/variable
* added option "mapped" which should be used instead of setting "property_path" to false * added option "mapped" which should be used instead of setting "property_path" to false
* "data_class" now *must* be set if a form maps to an object and should be left empty otherwise * [BC BREAK] "data_class" now *must* be set if a form maps to an object and should be left empty otherwise
* improved error mapping on forms * improved error mapping on forms
* dot (".") rules are now allowed to map errors assigned to a form to * dot (".") rules are now allowed to map errors assigned to a form to
one of its children one of its children
* errors are not mapped to unsynchronized forms anymore * errors are not mapped to unsynchronized forms anymore
* changed Form constructor to accept a single `FormConfigInterface` object * [BC BREAK] changed Form constructor to accept a single `FormConfigInterface` object
* changed argument order in the FormBuilder constructor * [BC BREAK] changed argument order in the FormBuilder constructor
* deprecated Form methods * deprecated Form methods
* `getTypes` * `getTypes`
* `getErrorBubbling` * `getErrorBubbling`
@ -85,3 +85,6 @@ CHANGELOG
* `getClientTransformers` * `getClientTransformers`
* deprecated the option "validation_constraint" in favor of the new * deprecated the option "validation_constraint" in favor of the new
option "constraints" option "constraints"
* removed superfluous methods from DataMapperInterface
* `mapFormToData`
* `mapDataToForm`

View File

@ -21,9 +21,5 @@ interface DataMapperInterface
*/ */
function mapDataToForms($data, array $forms); function mapDataToForms($data, array $forms);
function mapDataToForm($data, FormInterface $form);
function mapFormsToData(array $forms, &$data); function mapFormsToData(array $forms, &$data);
function mapFormToData(FormInterface $form, &$data);
} }

View File

@ -32,28 +32,13 @@ class PropertyPathMapper implements DataMapperInterface
$iterator = new \RecursiveIteratorIterator($iterator); $iterator = new \RecursiveIteratorIterator($iterator);
foreach ($iterator as $form) { foreach ($iterator as $form) {
$this->mapDataToForm($data, $form); /* @var FormInterface $form */
} $propertyPath = $form->getPropertyPath();
} $config = $form->getConfig();
}
/** if (null !== $propertyPath && $config->getMapped()) {
* {@inheritdoc} $form->setData($propertyPath->getValue($data));
*/
public function mapDataToForm($data, FormInterface $form)
{
if (!empty($data)) {
$propertyPath = $form->getPropertyPath();
$config = $form->getConfig();
if (null !== $propertyPath && $config->getMapped()) {
$propertyData = $propertyPath->getValue($data);
if (is_object($propertyData) && !$form->getAttribute('by_reference')) {
$propertyData = clone $propertyData;
} }
$form->setData($propertyData);
} }
} }
} }
@ -67,28 +52,20 @@ class PropertyPathMapper implements DataMapperInterface
$iterator = new \RecursiveIteratorIterator($iterator); $iterator = new \RecursiveIteratorIterator($iterator);
foreach ($iterator as $form) { foreach ($iterator as $form) {
$this->mapFormToData($form, $data); /* @var FormInterface $form */
} $propertyPath = $form->getPropertyPath();
} $config = $form->getConfig();
/** // Write-back is disabled if the form is not synchronized (transformation failed)
* {@inheritdoc} // and if the form is disabled (modification not allowed)
*/ if (null !== $propertyPath && $config->getMapped() && $form->isSynchronized() && !$form->isDisabled()) {
public function mapFormToData(FormInterface $form, &$data) // If the data is identical to the value in $data, we are
{ // dealing with a reference
$propertyPath = $form->getPropertyPath(); $isReference = $form->getData() === $propertyPath->getValue($data);
$config = $form->getConfig();
// Write-back is disabled if the form is not synchronized (transformation failed) if (!is_object($data) || !$isReference || !$config->getByReference()) {
// and if the form is disabled (modification not allowed) $propertyPath->setValue($data, $form->getData());
if (null !== $propertyPath && $config->getMapped() && $form->isSynchronized() && !$form->isDisabled()) { }
// If the data is identical to the value in $data, we are
// dealing with a reference
$isReference = $form->getData() === $propertyPath->getValue($data);
$byReference = $form->getAttribute('by_reference');
if (!(is_object($data) && $isReference && $byReference)) {
$propertyPath->setValue($data, $form->getData());
} }
} }
} }

View File

@ -46,9 +46,9 @@ class FormType extends AbstractType
// BC compatibility, when "property_path" could be false // BC compatibility, when "property_path" could be false
->setPropertyPath(is_string($options['property_path']) ? $options['property_path'] : null) ->setPropertyPath(is_string($options['property_path']) ? $options['property_path'] : null)
->setMapped($options['mapped']) ->setMapped($options['mapped'])
->setByReference($options['by_reference'])
->setVirtual($options['virtual']) ->setVirtual($options['virtual'])
->setAttribute('read_only', $options['read_only']) ->setAttribute('read_only', $options['read_only'])
->setAttribute('by_reference', $options['by_reference'])
->setAttribute('max_length', $options['max_length']) ->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern']) ->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName())) ->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))

View File

@ -319,6 +319,10 @@ class Form implements \IteratorAggregate, FormInterface
throw new AlreadyBoundException('You cannot change the data of a bound form'); throw new AlreadyBoundException('You cannot change the data of a bound form');
} }
if (is_object($appData) && !$this->config->getByReference()) {
$appData = clone $appData;
}
$event = new DataEvent($this, $appData); $event = new DataEvent($this, $appData);
$this->config->getEventDispatcher()->dispatch(FormEvents::PRE_SET_DATA, $event); $this->config->getEventDispatcher()->dispatch(FormEvents::PRE_SET_DATA, $event);
@ -805,7 +809,7 @@ class Form implements \IteratorAggregate, FormInterface
$child->setParent($this); $child->setParent($this);
if ($this->config->getDataMapper()) { if ($this->config->getDataMapper()) {
$this->config->getDataMapper()->mapDataToForm($this->getClientData(), $child); $this->config->getDataMapper()->mapDataToForms($this->getClientData(), array($child));
} }
return $this; return $this;

View File

@ -41,12 +41,17 @@ class FormConfig implements FormConfigInterface
/** /**
* @var Boolean * @var Boolean
*/ */
private $mapped; private $mapped = true;
/** /**
* @var Boolean * @var Boolean
*/ */
private $virtual; private $byReference = true;
/**
* @var Boolean
*/
private $virtual = false;
/** /**
* @var array * @var array
@ -76,17 +81,17 @@ class FormConfig implements FormConfigInterface
/** /**
* @var Boolean * @var Boolean
*/ */
private $required; private $required = true;
/** /**
* @var Boolean * @var Boolean
*/ */
private $disabled; private $disabled = false;
/** /**
* @var Boolean * @var Boolean
*/ */
private $errorBubbling; private $errorBubbling = false;
/** /**
* @var mixed * @var mixed
@ -294,6 +299,14 @@ class FormConfig implements FormConfigInterface
return $this->mapped; return $this->mapped;
} }
/**
* {@inheritdoc}
*/
public function getByReference()
{
return $this->byReference;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -550,6 +563,21 @@ class FormConfig implements FormConfigInterface
return $this; return $this;
} }
/**
* Sets whether the form's data should be modified by reference.
*
* @param Boolean $byReference Whether the data should be
modified by reference.
*
* @return self The configuration object.
*/
public function setByReference($byReference)
{
$this->byReference = $byReference;
return $this;
}
/** /**
* Sets whether the form should be virtual. * Sets whether the form should be virtual.
* *

View File

@ -47,6 +47,13 @@ interface FormConfigInterface
*/ */
function getMapped(); function getMapped();
/**
* Returns whether the form's data should be modified by reference.
*
* @return Boolean Whether to modify the form's data by reference.
*/
function getByReference();
/** /**
* Returns whether the form should be virtual. * Returns whether the form should be virtual.
* *

View File

@ -12,6 +12,9 @@
namespace Symfony\Component\Form\Tests\Extension\Core\DataMapper; namespace Symfony\Component\Form\Tests\Extension\Core\DataMapper;
use Symfony\Component\Form\Tests\FormInterface; use Symfony\Component\Form\Tests\FormInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormConfig;
use Symfony\Component\Form\FormConfigInterface;
use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
@ -44,18 +47,30 @@ abstract class PropertyPathMapperTest_Form implements FormInterface
class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
{ {
/**
* @var PropertyPathMapper
*/
private $mapper; private $mapper;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dispatcher;
protected function setUp() protected function setUp()
{ {
if (!class_exists('Symfony\Component\EventDispatcher\Event')) {
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->mapper = new PropertyPathMapper(); $this->mapper = new PropertyPathMapper();
} }
protected function tearDown() /**
{ * @param $path
$this->mapper = null; * @return \PHPUnit_Framework_MockObject_MockObject
} */
private function getPropertyPath($path) private function getPropertyPath($path)
{ {
return $this->getMockBuilder('Symfony\Component\Form\Util\PropertyPath') return $this->getMockBuilder('Symfony\Component\Form\Util\PropertyPath')
@ -64,44 +79,26 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
->getMock(); ->getMock();
} }
private function getForm(PropertyPath $propertyPath = null, $byReference, $synchronized = true, $mapped = true, $disabled = false) /**
* @param FormConfigInterface $config
* @param Boolean $synchronized
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function getForm(FormConfigInterface $config, $synchronized = true)
{ {
$config = $this->getMock('Symfony\Component\Form\FormConfigInterface'); $form = $this->getMockBuilder('Symfony\Component\Form\Form')
->setConstructorArgs(array($config))
$config->expects($this->any()) ->setMethods(array('isSynchronized'))
->method('getMapped') ->getMock();
->will($this->returnValue($mapped));
$form = $this->getMockBuilder(__CLASS__ . '_Form')
// PHPUnit's getMockForAbstractClass does not behave like in the docs..
// If the array is empty, all methods are mocked. If it is not
// empty, only abstract methods and the methods in the array are
// mocked.
->setMethods(array('foo'))
->getMockForAbstractClass();
$form->setAttribute('by_reference', $byReference);
$form->expects($this->any())
->method('getConfig')
->will($this->returnValue($config));
$form->expects($this->any())
->method('getPropertyPath')
->will($this->returnValue($propertyPath));
$form->expects($this->any()) $form->expects($this->any())
->method('isSynchronized') ->method('isSynchronized')
->will($this->returnValue($synchronized)); ->will($this->returnValue($synchronized));
$form->expects($this->any())
->method('isDisabled')
->will($this->returnValue($disabled));
return $form; return $form;
} }
public function testMapDataToFormPassesObjectRefIfByReference() public function testMapDataToFormsPassesObjectRefIfByReference()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -112,16 +109,19 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
->with($car) ->with($car)
->will($this->returnValue($engine)); ->will($this->returnValue($engine));
$form = $this->getForm($propertyPath, true); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$config->setByReference(true);
$config->setPropertyPath($propertyPath);
$form = $this->getForm($config);
$this->mapper->mapDataToForm($car, $form); $this->mapper->mapDataToForms($car, array($form));
// Can't use isIdentical() above because mocks always clone their // Can't use isIdentical() above because mocks always clone their
// arguments which can't be disabled in PHPUnit 3.6 // arguments which can't be disabled in PHPUnit 3.6
$this->assertSame($engine, $form->getData()); $this->assertSame($engine, $form->getData());
} }
public function testMapDataToFormPassesObjectCloneIfNotByReference() public function testMapDataToFormsPassesObjectCloneIfNotByReference()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -132,26 +132,33 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
->with($car) ->with($car)
->will($this->returnValue($engine)); ->will($this->returnValue($engine));
$form = $this->getForm($propertyPath, false); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$config->setByReference(false);
$config->setPropertyPath($propertyPath);
$form = $this->getForm($config);
$this->mapper->mapDataToForm($car, $form); $this->mapper->mapDataToForms($car, array($form));
$this->assertNotSame($engine, $form->getData()); $this->assertNotSame($engine, $form->getData());
$this->assertEquals($engine, $form->getData()); $this->assertEquals($engine, $form->getData());
} }
public function testMapDataToFormIgnoresEmptyPropertyPath() public function testMapDataToFormsIgnoresEmptyPropertyPath()
{ {
$car = new \stdClass(); $car = new \stdClass();
$form = $this->getForm(null, true); $config = new FormConfig(null, '\stdClass', $this->dispatcher);
$config->setByReference(true);
$form = $this->getForm($config);
$this->mapper->mapDataToForm($car, $form); $this->assertNull($form->getPropertyPath());
$this->mapper->mapDataToForms($car, array($form));
$this->assertNull($form->getData()); $this->assertNull($form->getData());
} }
public function testMapDataToFormIgnoresUnmapped() public function testMapDataToFormsIgnoresUnmapped()
{ {
$car = new \stdClass(); $car = new \stdClass();
$propertyPath = $this->getPropertyPath('engine'); $propertyPath = $this->getPropertyPath('engine');
@ -159,24 +166,64 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
$propertyPath->expects($this->never()) $propertyPath->expects($this->never())
->method('getValue'); ->method('getValue');
$form = $this->getForm($propertyPath, true, true, false); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$config->setByReference(true);
$config->setMapped(false);
$config->setPropertyPath($propertyPath);
$form = $this->getForm($config);
$this->mapper->mapDataToForm($car, $form); $this->mapper->mapDataToForms($car, array($form));
$this->assertNull($form->getData()); $this->assertNull($form->getData());
} }
public function testMapDataToFormIgnoresEmptyData() public function testMapDataToFormsIgnoresEmptyData()
{ {
$propertyPath = $this->getPropertyPath('engine'); $propertyPath = $this->getPropertyPath('engine');
$form = $this->getForm($propertyPath, true);
$this->mapper->mapDataToForm(null, $form); $propertyPath->expects($this->never())
->method('getValue');
$config = new FormConfig('name', '\stdClass', $this->dispatcher);
$config->setByReference(true);
$config->setPropertyPath($propertyPath);
$form = $this->getForm($config);
$this->mapper->mapDataToForms(null, array($form));
$this->assertNull($form->getData()); $this->assertNull($form->getData());
} }
public function testMapFormToDataWritesBackIfNotByReference() public function testMapDataToFormsSkipsVirtualForms()
{
$car = new \stdClass();
$engine = new \stdClass();
$propertyPath = $this->getPropertyPath('engine');
$propertyPath->expects($this->once())
->method('getValue')
->with($car)
->will($this->returnValue($engine));
$config = new FormConfig('name', '\stdClass', $this->dispatcher);
$config->setByReference(true);
$config->setVirtual(true);
$form = $this->getForm($config);
$config = new FormConfig('engine', '\stdClass', $this->dispatcher);
$config->setByReference(true);
$config->setPropertyPath($propertyPath);
$child = $this->getForm($config);
$form->add($child);
$this->mapper->mapDataToForms($car, array($form));
$this->assertNull($form->getData());
$this->assertSame($engine, $child->getData());
}
public function testMapFormsToDataWritesBackIfNotByReference()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -186,13 +233,16 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
->method('setValue') ->method('setValue')
->with($car, $engine); ->with($car, $engine);
$form = $this->getForm($propertyPath, false); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData($engine); $config->setByReference(false);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$form = $this->getForm($config);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
} }
public function testMapFormToDataWritesBackIfByReferenceButNoReference() public function testMapFormsToDataWritesBackIfByReferenceButNoReference()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -202,13 +252,16 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
->method('setValue') ->method('setValue')
->with($car, $engine); ->with($car, $engine);
$form = $this->getForm($propertyPath, true); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData($engine); $config->setByReference(true);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$form = $this->getForm($config);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
} }
public function testMapFormToDataWritesBackIfByReferenceAndReference() public function testMapFormsToDataWritesBackIfByReferenceAndReference()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -223,13 +276,16 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
$propertyPath->expects($this->never()) $propertyPath->expects($this->never())
->method('setValue'); ->method('setValue');
$form = $this->getForm($propertyPath, true); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData($engine); $config->setByReference(true);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$form = $this->getForm($config);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
} }
public function testMapFormToDataIgnoresUnmapped() public function testMapFormsToDataIgnoresUnmapped()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -238,13 +294,17 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
$propertyPath->expects($this->never()) $propertyPath->expects($this->never())
->method('setValue'); ->method('setValue');
$form = $this->getForm($propertyPath, true, true, false); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData($engine); $config->setByReference(true);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$config->setMapped(false);
$form = $this->getForm($config);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
} }
public function testMapFormToDataIgnoresEmptyData() public function testMapFormsToDataIgnoresEmptyData()
{ {
$car = new \stdClass(); $car = new \stdClass();
$propertyPath = $this->getPropertyPath('engine'); $propertyPath = $this->getPropertyPath('engine');
@ -252,13 +312,16 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
$propertyPath->expects($this->never()) $propertyPath->expects($this->never())
->method('setValue'); ->method('setValue');
$form = $this->getForm($propertyPath, true); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData(null); $config->setByReference(true);
$config->setPropertyPath($propertyPath);
$config->setData(null);
$form = $this->getForm($config);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
} }
public function testMapFormToDataIgnoresUnsynchronized() public function testMapFormsToDataIgnoresUnsynchronized()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -267,13 +330,16 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
$propertyPath->expects($this->never()) $propertyPath->expects($this->never())
->method('setValue'); ->method('setValue');
$form = $this->getForm($propertyPath, true, false); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData($engine); $config->setByReference(true);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$form = $this->getForm($config, false);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
} }
public function testMapFormToDataIgnoresDisabled() public function testMapFormsToDataIgnoresDisabled()
{ {
$car = new \stdClass(); $car = new \stdClass();
$engine = new \stdClass(); $engine = new \stdClass();
@ -282,9 +348,45 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
$propertyPath->expects($this->never()) $propertyPath->expects($this->never())
->method('setValue'); ->method('setValue');
$form = $this->getForm($propertyPath, true, true, true, true); $config = new FormConfig('name', '\stdClass', $this->dispatcher);
$form->setData($engine); $config->setByReference(true);
$config->setPropertyPath($propertyPath);
$config->setData($engine);
$config->setDisabled(true);
$form = $this->getForm($config);
$this->mapper->mapFormToData($form, $car); $this->mapper->mapFormsToData(array($form), $car);
}
public function testMapFormsToDataSkipsVirtualForms()
{
$car = new \stdClass();
$engine = new \stdClass();
$parentPath = $this->getPropertyPath('name');
$childPath = $this->getPropertyPath('engine');
$parentPath->expects($this->never())
->method('getValue');
$parentPath->expects($this->never())
->method('setValue');
$childPath->expects($this->once())
->method('setValue')
->with($car, $engine);
$config = new FormConfig('name', '\stdClass', $this->dispatcher);
$config->setPropertyPath($parentPath);
$config->setVirtual(true);
$form = $this->getForm($config);
$config = new FormConfig('engine', '\stdClass', $this->dispatcher);
$config->setByReference(true);
$config->setPropertyPath($childPath);
$config->setData($engine);
$child = $this->getForm($config);
$form->add($child);
$this->mapper->mapFormsToData(array($form), $car);
} }
} }

View File

@ -464,6 +464,25 @@ class FormTest extends \PHPUnit_Framework_TestCase
$this->form->setData(null); $this->form->setData(null);
} }
public function testSetDataClonesObjectIfNotByReference()
{
$data = new \stdClass();
$form = $this->getBuilder('name', null, '\stdClass')->setByReference(false)->getForm();
$form->setData($data);
$this->assertNotSame($data, $form->getData());
$this->assertEquals($data, $form->getData());
}
public function testSetDataDoesNotCloneObjectIfByReference()
{
$data = new \stdClass();
$form = $this->getBuilder('name', null, '\stdClass')->setByReference(true)->getForm();
$form->setData($data);
$this->assertSame($data, $form->getData());
}
public function testSetDataExecutesTransformationChain() public function testSetDataExecutesTransformationChain()
{ {
// use real event dispatcher now // use real event dispatcher now
@ -736,8 +755,8 @@ class FormTest extends \PHPUnit_Framework_TestCase
$child = $this->getBuilder()->getForm(); $child = $this->getBuilder()->getForm();
$mapper->expects($this->once()) $mapper->expects($this->once())
->method('mapDataToForm') ->method('mapDataToForms')
->with('bar', $child); ->with('bar', array($child));
$form->add($child); $form->add($child);
} }

View File

@ -42,6 +42,11 @@ class UnmodifiableFormConfig implements FormConfigInterface
*/ */
private $mapped; private $mapped;
/**
* @var Boolean
*/
private $byReference;
/** /**
* @var Boolean * @var Boolean
*/ */
@ -123,6 +128,7 @@ class UnmodifiableFormConfig implements FormConfigInterface
$this->name = $config->getName(); $this->name = $config->getName();
$this->propertyPath = $config->getPropertyPath(); $this->propertyPath = $config->getPropertyPath();
$this->mapped = $config->getMapped(); $this->mapped = $config->getMapped();
$this->byReference = $config->getByReference();
$this->virtual = $config->getVirtual(); $this->virtual = $config->getVirtual();
$this->types = $config->getTypes(); $this->types = $config->getTypes();
$this->clientTransformers = $config->getClientTransformers(); $this->clientTransformers = $config->getClientTransformers();
@ -170,6 +176,14 @@ class UnmodifiableFormConfig implements FormConfigInterface
return $this->mapped; return $this->mapped;
} }
/**
* {@inheritdoc}
*/
public function getByReference()
{
return $this->byReference;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */