[Form] Fixed: Empty forms can be compound too

This commit is contained in:
Bernhard Schussek 2012-07-03 19:09:41 +02:00
parent 85977649a4
commit 45d967e95e
12 changed files with 204 additions and 108 deletions

View File

@ -24,7 +24,7 @@ class PropertyPathMapper implements DataMapperInterface
public function mapDataToForms($data, array $forms) public function mapDataToForms($data, array $forms)
{ {
if (!empty($data) && !is_array($data) && !is_object($data)) { if (!empty($data) && !is_array($data) && !is_object($data)) {
throw new UnexpectedTypeException($data, 'Object, array or empty'); throw new UnexpectedTypeException($data, 'object, array or empty');
} }
if (!empty($data)) { if (!empty($data)) {

View File

@ -42,6 +42,7 @@ class FormType extends AbstractType
->setMapped($options['mapped']) ->setMapped($options['mapped'])
->setByReference($options['by_reference']) ->setByReference($options['by_reference'])
->setVirtual($options['virtual']) ->setVirtual($options['virtual'])
->setCompound($options['compound'])
->setData($options['data']) ->setData($options['data'])
->setDataMapper(new PropertyPathMapper()) ->setDataMapper(new PropertyPathMapper())
; ;
@ -116,7 +117,7 @@ class FormType extends AbstractType
'multipart' => false, 'multipart' => false,
'attr' => $options['attr'], 'attr' => $options['attr'],
'label_attr' => $options['label_attr'], 'label_attr' => $options['label_attr'],
'compound' => $options['compound'], 'compound' => $form->getConfig()->getCompound(),
'types' => $types, 'types' => $types,
'translation_domain' => $translationDomain, 'translation_domain' => $translationDomain,
)); ));
@ -160,7 +161,7 @@ class FormType extends AbstractType
} }
return function (FormInterface $form) { return function (FormInterface $form) {
return count($form) > 0 ? array() : ''; return $form->getConfig()->getCompound() ? array() : '';
}; };
}; };

View File

@ -345,7 +345,7 @@ class Form implements \IteratorAggregate, FormInterface
$viewData = $this->normToView($normData); $viewData = $this->normToView($normData);
// Validate if view data matches data class (unless empty) // Validate if view data matches data class (unless empty)
if (!empty($viewData)) { if ('' !== $viewData && null !== $viewData) {
$dataClass = $this->config->getDataClass(); $dataClass = $this->config->getDataClass();
$actualType = is_object($viewData) ? 'an instance of class ' . get_class($viewData) : ' a(n) ' . gettype($viewData); $actualType = is_object($viewData) ? 'an instance of class ' . get_class($viewData) : ' a(n) ' . gettype($viewData);
@ -378,7 +378,7 @@ class Form implements \IteratorAggregate, FormInterface
$this->viewData = $viewData; $this->viewData = $viewData;
$this->synchronized = true; $this->synchronized = true;
if (count($this->children) > 0 && $this->config->getDataMapper()) { if ($this->config->getCompound() && $this->config->getDataMapper()) {
// Update child forms from the data // Update child forms from the data
$this->config->getDataMapper()->mapDataToForms($viewData, $this->children); $this->config->getDataMapper()->mapDataToForms($viewData, $this->children);
} }
@ -477,37 +477,42 @@ class Form implements \IteratorAggregate, FormInterface
$this->config->getEventDispatcher()->dispatch(FormEvents::BIND_CLIENT_DATA, $event); $this->config->getEventDispatcher()->dispatch(FormEvents::BIND_CLIENT_DATA, $event);
$submittedData = $event->getData(); $submittedData = $event->getData();
// Build the data in the view format // Check whether the form is compound.
$viewData = $submittedData; // This check is preferrable over checking the number of children,
// since forms without children may also be compound.
if (count($this->children) > 0) { // (think of empty collection forms)
if (null === $viewData || '' === $viewData) { if ($this->config->getCompound()) {
$viewData = array(); if (null === $submittedData || '' === $submittedData) {
$submittedData = array();
} }
if (!is_array($viewData)) { if (!is_array($submittedData)) {
throw new UnexpectedTypeException($viewData, 'array'); throw new UnexpectedTypeException($submittedData, 'array');
} }
foreach ($this->children as $name => $child) { foreach ($this->children as $name => $child) {
if (!isset($viewData[$name])) { if (!isset($submittedData[$name])) {
$viewData[$name] = null; $submittedData[$name] = null;
} }
} }
foreach ($viewData as $name => $value) { foreach ($submittedData as $name => $value) {
if ($this->has($name)) { if ($this->has($name)) {
$this->children[$name]->bind($value); $this->children[$name]->bind($value);
} else { } else {
$extraData[$name] = $value; $extraData[$name] = $value;
} }
} }
}
// If we have a data mapper, use old view data and merge // By default, the submitted data is also the data in view format
// data from the children into it later $viewData = $submittedData;
if ($this->config->getDataMapper()) {
$viewData = $this->getViewData(); // If the form is compound, the default data in view format
} // is reused. The data of the children is merged into this
// default data using the data mapper.
if ($this->config->getCompound() && $this->config->getDataMapper()) {
$viewData = $this->getViewData();
} }
if (null === $viewData || '' === $viewData) { if (null === $viewData || '' === $viewData) {
@ -522,7 +527,7 @@ class Form implements \IteratorAggregate, FormInterface
} }
// Merge form data from children into existing view data // Merge form data from children into existing view data
if (count($this->children) > 0 && $this->config->getDataMapper() && null !== $viewData) { if ($this->config->getCompound() && $this->config->getDataMapper() && null !== $viewData) {
$this->config->getDataMapper()->mapFormsToData($this->children, $viewData); $this->config->getDataMapper()->mapFormsToData($this->children, $viewData);
} }
@ -542,7 +547,6 @@ class Form implements \IteratorAggregate, FormInterface
$this->config->getEventDispatcher()->dispatch(FormEvents::BIND_NORM_DATA, $event); $this->config->getEventDispatcher()->dispatch(FormEvents::BIND_NORM_DATA, $event);
$normData = $event->getData(); $normData = $event->getData();
// Synchronize representations - must not change the content! // Synchronize representations - must not change the content!
$modelData = $this->normToModel($normData); $modelData = $this->normToModel($normData);
$viewData = $this->normToView($normData); $viewData = $this->normToView($normData);
@ -591,7 +595,7 @@ class Form implements \IteratorAggregate, FormInterface
// Form bound without name // Form bound without name
$params = $request->request->all(); $params = $request->request->all();
$files = $request->files->all(); $files = $request->files->all();
} elseif (count($this->children) > 0) { } elseif ($this->config->getCompound()) {
// Form bound with name and children // Form bound with name and children
$params = $request->request->get($name, array()); $params = $request->request->get($name, array());
$files = $request->files->get($name, array()); $files = $request->files->get($name, array());
@ -841,6 +845,10 @@ class Form implements \IteratorAggregate, FormInterface
throw new AlreadyBoundException('You cannot add children to a bound form'); throw new AlreadyBoundException('You cannot add children to a bound form');
} }
if (!$this->config->getCompound()) {
throw new FormException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?');
}
$this->children[$child->getName()] = $child; $this->children[$child->getName()] = $child;
$child->setParent($this); $child->setParent($this);

View File

@ -53,6 +53,11 @@ class FormConfig implements FormConfigEditorInterface
*/ */
private $virtual = false; private $virtual = false;
/**
* @var Boolean
*/
private $compound = true;
/** /**
* @var array * @var array
*/ */
@ -356,6 +361,14 @@ class FormConfig implements FormConfigEditorInterface
return $this->virtual; return $this->virtual;
} }
/**
* {@inheritdoc}
*/
public function getCompound()
{
return $this->compound;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -632,6 +645,16 @@ class FormConfig implements FormConfigEditorInterface
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function setCompound($compound)
{
$this->compound = $compound;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -199,6 +199,17 @@ interface FormConfigEditorInterface extends FormConfigInterface
*/ */
function setVirtual($virtual); function setVirtual($virtual);
/**
* Sets whether the form should be compound.
*
* @param Boolean $compound Whether the form should be compound.
*
* @return self The configuration object.
*
* @see FormConfigInterface::getCompound()
*/
function setCompound($compound);
/** /**
* Set the types. * Set the types.
* *

View File

@ -65,6 +65,17 @@ interface FormConfigInterface
*/ */
function getVirtual(); function getVirtual();
/**
* Returns whether the form is compound.
*
* This property is independent of whether the form actually has
* children. A form can be compound and have no children at all, like
* for example an empty collection form.
*
* @return Boolean Whether the form is compound.
*/
function getCompound();
/** /**
* Returns the form types used to construct the form. * Returns the form types used to construct the form.
* *

View File

@ -18,7 +18,7 @@ class CollectionTypeTest extends TypeTestCase
public function testContainsNoChildByDefault() public function testContainsNoChildByDefault()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
)); ));
$this->assertCount(0, $form); $this->assertCount(0, $form);
@ -27,7 +27,7 @@ class CollectionTypeTest extends TypeTestCase
public function testSetDataAdjustsSize() public function testSetDataAdjustsSize()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
'options' => array( 'options' => array(
'max_length' => 20, 'max_length' => 20,
), ),
@ -53,7 +53,7 @@ class CollectionTypeTest extends TypeTestCase
public function testThrowsExceptionIfObjectIsNotTraversable() public function testThrowsExceptionIfObjectIsNotTraversable()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
)); ));
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException'); $this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
$form->setData(new \stdClass()); $form->setData(new \stdClass());
@ -62,7 +62,7 @@ class CollectionTypeTest extends TypeTestCase
public function testNotResizedIfBoundWithMissingData() public function testNotResizedIfBoundWithMissingData()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
)); ));
$form->setData(array('foo@foo.com', 'bar@bar.com')); $form->setData(array('foo@foo.com', 'bar@bar.com'));
$form->bind(array('foo@bar.com')); $form->bind(array('foo@bar.com'));
@ -70,13 +70,13 @@ class CollectionTypeTest extends TypeTestCase
$this->assertTrue($form->has('0')); $this->assertTrue($form->has('0'));
$this->assertTrue($form->has('1')); $this->assertTrue($form->has('1'));
$this->assertEquals('foo@bar.com', $form[0]->getData()); $this->assertEquals('foo@bar.com', $form[0]->getData());
$this->assertNull($form[1]->getData()); $this->assertEquals('', $form[1]->getData());
} }
public function testResizedDownIfBoundWithMissingDataAndAllowDelete() public function testResizedDownIfBoundWithMissingDataAndAllowDelete()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
'allow_delete' => true, 'allow_delete' => true,
)); ));
$form->setData(array('foo@foo.com', 'bar@bar.com')); $form->setData(array('foo@foo.com', 'bar@bar.com'));
@ -91,7 +91,7 @@ class CollectionTypeTest extends TypeTestCase
public function testNotResizedIfBoundWithExtraData() public function testNotResizedIfBoundWithExtraData()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
)); ));
$form->setData(array('foo@bar.com')); $form->setData(array('foo@bar.com'));
$form->bind(array('foo@foo.com', 'bar@bar.com')); $form->bind(array('foo@foo.com', 'bar@bar.com'));
@ -104,7 +104,7 @@ class CollectionTypeTest extends TypeTestCase
public function testResizedUpIfBoundWithExtraDataAndAllowAdd() public function testResizedUpIfBoundWithExtraDataAndAllowAdd()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'form', 'type' => 'text',
'allow_add' => true, 'allow_add' => true,
)); ));
$form->setData(array('foo@bar.com')); $form->setData(array('foo@bar.com'));

View File

@ -77,6 +77,7 @@ class FormTypeTest extends TypeTestCase
null => '', null => '',
'reverse[a]' => 'a', 'reverse[a]' => 'a',
))) )))
->setCompound(false)
->getForm(); ->getForm();
$form->bind(' a '); $form->bind(' a ');
@ -92,6 +93,7 @@ class FormTypeTest extends TypeTestCase
null => '', null => '',
'reverse[ a ]' => ' a ', 'reverse[ a ]' => ' a ',
))) )))
->setCompound(false)
->getForm(); ->getForm();
$form->bind(' a '); $form->bind(' a ');
@ -235,8 +237,8 @@ class FormTypeTest extends TypeTestCase
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => false, 'required' => false,
)); ));
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->add($this->factory->createNamed('lastName', 'form')); $form->add($this->factory->createNamed('lastName', 'text'));
$form->setData(null); $form->setData(null);
// partially empty, still an object is created // partially empty, still an object is created
@ -256,8 +258,8 @@ class FormTypeTest extends TypeTestCase
'data' => new Author(), 'data' => new Author(),
'required' => false, 'required' => false,
)); ));
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->add($this->factory->createNamed('lastName', 'form')); $form->add($this->factory->createNamed('lastName', 'text'));
$form->setData(null); $form->setData(null);
// partially empty, still an object is created // partially empty, still an object is created
@ -276,7 +278,7 @@ class FormTypeTest extends TypeTestCase
'data_class' => null, 'data_class' => null,
'required' => false, 'required' => false,
)); ));
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->setData(null); $form->setData(null);
$form->bind(array('firstName' => 'Bernhard')); $form->bind(array('firstName' => 'Bernhard'));
@ -290,8 +292,8 @@ class FormTypeTest extends TypeTestCase
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => false, 'required' => false,
)); ));
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->add($this->factory->createNamed('lastName', 'form')); $form->add($this->factory->createNamed('lastName', 'text'));
$form->setData(null); $form->setData(null);
$form->bind(array('firstName' => '', 'lastName' => '')); $form->bind(array('firstName' => '', 'lastName' => ''));
@ -305,8 +307,8 @@ class FormTypeTest extends TypeTestCase
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => true, 'required' => true,
)); ));
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->add($this->factory->createNamed('lastName', 'form')); $form->add($this->factory->createNamed('lastName', 'text'));
$form->setData(null); $form->setData(null);
$form->bind(array('firstName' => '', 'lastName' => '')); $form->bind(array('firstName' => '', 'lastName' => ''));
@ -320,7 +322,7 @@ class FormTypeTest extends TypeTestCase
public function testBindWithEmptyDataStoresArrayIfNoClassAvailable() public function testBindWithEmptyDataStoresArrayIfNoClassAvailable()
{ {
$form = $this->factory->create('form'); $form = $this->factory->create('form');
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->setData(null); $form->setData(null);
$form->bind(array('firstName' => 'Bernhard')); $form->bind(array('firstName' => 'Bernhard'));
@ -328,7 +330,7 @@ class FormTypeTest extends TypeTestCase
$this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); $this->assertSame(array('firstName' => 'Bernhard'), $form->getData());
} }
public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNoChildren() public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNotCompound()
{ {
$form = $this->factory->createBuilder('form') $form = $this->factory->createBuilder('form')
->addViewTransformer(new FixedDataTransformer(array( ->addViewTransformer(new FixedDataTransformer(array(
@ -337,6 +339,7 @@ class FormTypeTest extends TypeTestCase
// required to test that bind(null) is converted to '' // required to test that bind(null) is converted to ''
'empty' => '', 'empty' => '',
))) )))
->setCompound(false)
->getForm(); ->getForm();
$form->bind(null); $form->bind(null);
@ -352,7 +355,7 @@ class FormTypeTest extends TypeTestCase
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'empty_data' => $author, 'empty_data' => $author,
)); ));
$form->add($this->factory->createNamed('firstName', 'form')); $form->add($this->factory->createNamed('firstName', 'text'));
$form->bind(array('firstName' => 'Bernhard')); $form->bind(array('firstName' => 'Bernhard'));
@ -360,34 +363,31 @@ class FormTypeTest extends TypeTestCase
$this->assertEquals('Bernhard', $author->firstName); $this->assertEquals('Bernhard', $author->firstName);
} }
public function provideZeros()
{
return array(
array(0, '0'),
array('0', '0'),
array('00000', '00000'),
);
}
/** /**
* @dataProvider provideZeros
* @see https://github.com/symfony/symfony/issues/1986 * @see https://github.com/symfony/symfony/issues/1986
*/ */
public function testSetDataThroughParamsWithZero() public function testSetDataThroughParamsWithZero($data, $dataAsString)
{ {
$form = $this->factory->create('form', null, array('data' => 0)); $form = $this->factory->create('form', null, array(
'data' => $data,
'compound' => false,
));
$view = $form->createView(); $view = $form->createView();
$this->assertFalse($form->isEmpty()); $this->assertFalse($form->isEmpty());
$this->assertSame('0', $view->getVar('value')); $this->assertSame($dataAsString, $view->getVar('value'));
$this->assertSame('0', $form->getData()); $this->assertSame($dataAsString, $form->getData());
$form = $this->factory->create('form', null, array('data' => '0'));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('0', $view->getVar('value'));
$this->assertSame('0', $form->getData());
$form = $this->factory->create('form', null, array('data' => '00000'));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('00000', $view->getVar('value'));
$this->assertSame('00000', $form->getData());
} }
/** /**
@ -412,14 +412,14 @@ class FormTypeTest extends TypeTestCase
$builder->add('reference', 'form', array( $builder->add('reference', 'form', array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
)); ));
$builder->get('reference')->add('firstName', 'form'); $builder->get('reference')->add('firstName', 'text');
$form = $builder->getForm(); $form = $builder->getForm();
$form->bind(array( $form->bind(array(
// reference has a getter, but not setter // reference has a getter, but not setter
'reference' => array( 'reference' => array(
'firstName' => 'Foo', 'firstName' => 'Foo',
) )
)); ));
$this->assertEquals('Foo', $author->getReference()->firstName); $this->assertEquals('Foo', $author->getReference()->firstName);
@ -435,7 +435,7 @@ class FormTypeTest extends TypeTestCase
$builder->add('referenceCopy', 'form', array( $builder->add('referenceCopy', 'form', array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
)); ));
$builder->get('referenceCopy')->add('firstName', 'form'); $builder->get('referenceCopy')->add('firstName', 'text');
$form = $builder->getForm(); $form = $builder->getForm();
$form['referenceCopy']->setData($newReference); // new author object $form['referenceCopy']->setData($newReference); // new author object
@ -459,7 +459,7 @@ class FormTypeTest extends TypeTestCase
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'by_reference' => false 'by_reference' => false
)); ));
$builder->get('referenceCopy')->add('firstName', 'form'); $builder->get('referenceCopy')->add('firstName', 'text');
$form = $builder->getForm(); $form = $builder->getForm();
$form->bind(array( $form->bind(array(

View File

@ -21,7 +21,7 @@ class RepeatedTypeTest extends TypeTestCase
parent::setUp(); parent::setUp();
$this->form = $this->factory->create('repeated', null, array( $this->form = $this->factory->create('repeated', null, array(
'type' => 'form', 'type' => 'text',
)); ));
$this->form->setData(null); $this->form->setData(null);
} }
@ -37,7 +37,7 @@ class RepeatedTypeTest extends TypeTestCase
public function testSetOptions() public function testSetOptions()
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
'type' => 'form', 'type' => 'text',
'options' => array('label' => 'Global'), 'options' => array('label' => 'Global'),
)); ));
@ -51,7 +51,7 @@ class RepeatedTypeTest extends TypeTestCase
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
// the global required value cannot be overriden // the global required value cannot be overriden
'type' => 'form', 'type' => 'text',
'first_options' => array('label' => 'Test', 'required' => false), 'first_options' => array('label' => 'Test', 'required' => false),
'second_options' => array('label' => 'Test2') 'second_options' => array('label' => 'Test2')
)); ));
@ -66,7 +66,7 @@ class RepeatedTypeTest extends TypeTestCase
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
'required' => false, 'required' => false,
'type' => 'form', 'type' => 'text',
)); ));
$this->assertFalse($form['first']->isRequired()); $this->assertFalse($form['first']->isRequired());
@ -76,7 +76,7 @@ class RepeatedTypeTest extends TypeTestCase
public function testSetOptionsPerChildAndOverwrite() public function testSetOptionsPerChildAndOverwrite()
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
'type' => 'form', 'type' => 'text',
'options' => array('label' => 'Label'), 'options' => array('label' => 'Label'),
'second_options' => array('label' => 'Second label') 'second_options' => array('label' => 'Second label')
)); ));

View File

@ -148,12 +148,14 @@ class FormTypeCsrfExtensionTest extends TypeTestCase
->will($this->returnValue($valid)); ->will($this->returnValue($valid));
$form = $this->factory $form = $this->factory
->create('form', null, array( ->createBuilder('form', null, array(
'csrf_field_name' => 'csrf', 'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider, 'csrf_provider' => $this->csrfProvider,
'intention' => '%INTENTION%', 'intention' => '%INTENTION%',
'compound' => true, 'compound' => true,
)); ))
->add('child', 'text')
->getForm();
$form->bind(array( $form->bind(array(
'child' => 'foobar', 'child' => 'foobar',
@ -173,12 +175,14 @@ class FormTypeCsrfExtensionTest extends TypeTestCase
->method('isCsrfTokenValid'); ->method('isCsrfTokenValid');
$form = $this->factory $form = $this->factory
->create('form', null, array( ->createBuilder('form', null, array(
'csrf_field_name' => 'csrf', 'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider, 'csrf_provider' => $this->csrfProvider,
'intention' => '%INTENTION%', 'intention' => '%INTENTION%',
'compound' => true, 'compound' => true,
)); ))
->add('child', 'text')
->getForm();
$form->bind(array( $form->bind(array(
'child' => 'foobar', 'child' => 'foobar',

View File

@ -31,8 +31,6 @@ class FormTest extends \PHPUnit_Framework_TestCase
private $factory; private $factory;
private $builder;
private $form; private $form;
protected function setUp() protected function setUp()
@ -279,14 +277,15 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testValidIfBound() public function testValidIfBound()
{ {
$this->form->bind('foobar'); $form = $this->getBuilder()->setCompound(false)->getForm();
$form->bind('foobar');
$this->assertTrue($this->form->isValid()); $this->assertTrue($form->isValid());
} }
public function testValidIfBoundAndDisabled() public function testValidIfBoundAndDisabled()
{ {
$form = $this->getBuilder()->setDisabled(true)->getForm(); $form = $this->getBuilder()->setCompound(false)->setDisabled(true)->getForm();
$form->bind('foobar'); $form->bind('foobar');
$this->assertTrue($form->isValid()); $this->assertTrue($form->isValid());
@ -318,10 +317,11 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testNotValidIfErrors() public function testNotValidIfErrors()
{ {
$this->form->bind('foobar'); $form = $this->getBuilder()->setCompound(false)->getForm();
$this->form->addError(new FormError('Error!')); $form->bind('foobar');
$form->addError(new FormError('Error!'));
$this->assertFalse($this->form->isValid()); $this->assertFalse($form->isValid());
} }
public function testNotValidIfChildNotValid() public function testNotValidIfChildNotValid()
@ -403,7 +403,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
*/ */
public function testRemoveThrowsExceptionIfAlreadyBound() public function testRemoveThrowsExceptionIfAlreadyBound()
{ {
$this->form->add($this->getBuilder('foo')->getForm()); $this->form->add($this->getBuilder('foo')->setCompound(false)->getForm());
$this->form->bind(array('foo' => 'bar')); $this->form->bind(array('foo' => 'bar'));
$this->form->remove('foo'); $this->form->remove('foo');
} }
@ -445,9 +445,10 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testBound() public function testBound()
{ {
$this->form->bind('foobar'); $form = $this->getBuilder()->setCompound(false)->getForm();
$form->bind('foobar');
$this->assertTrue($this->form->isBound()); $this->assertTrue($form->isBound());
} }
public function testNotBound() public function testNotBound()
@ -586,7 +587,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
*/ */
public function testSetDataConvertsNullToStringIfNoTransformer() public function testSetDataConvertsNullToStringIfNoTransformer()
{ {
$form = $this->getBuilder()->getForm(); $form = $this->getBuilder()->setCompound(false)->getForm();
$form->setData(null); $form->setData(null);
@ -597,7 +598,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testBindConvertsEmptyToNullIfNoTransformer() public function testBindConvertsEmptyToNullIfNoTransformer()
{ {
$form = $this->getBuilder()->getForm(); $form = $this->getBuilder()->setCompound(false)->getForm();
$form->bind(''); $form->bind('');
@ -641,6 +642,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testBindExecutesViewTransformersInReverseOrder() public function testBindExecutesViewTransformersInReverseOrder()
{ {
$form = $this->getBuilder() $form = $this->getBuilder()
->setCompound(false)
->addViewTransformer(new FixedDataTransformer(array( ->addViewTransformer(new FixedDataTransformer(array(
'' => '', '' => '',
'third' => 'second', 'third' => 'second',
@ -659,6 +661,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testBindExecutesModelTransformersInOrder() public function testBindExecutesModelTransformersInOrder()
{ {
$form = $this->getBuilder() $form = $this->getBuilder()
->setCompound(false)
->addModelTransformer(new FixedDataTransformer(array( ->addModelTransformer(new FixedDataTransformer(array(
'' => '', '' => '',
'second' => 'first', 'second' => 'first',
@ -681,9 +684,10 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testSynchronizedAfterBinding() public function testSynchronizedAfterBinding()
{ {
$this->form->bind('foobar'); $form = $this->getBuilder()->setCompound(false)->getForm();
$form->bind('foobar');
$this->assertTrue($this->form->isSynchronized()); $this->assertTrue($form->isSynchronized());
} }
public function testNotSynchronizedIfTransformationFailed() public function testNotSynchronizedIfTransformationFailed()
@ -694,6 +698,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
->will($this->throwException(new TransformationFailedException())); ->will($this->throwException(new TransformationFailedException()));
$form = $this->getBuilder() $form = $this->getBuilder()
->setCompound(false)
->addViewTransformer($transformer) ->addViewTransformer($transformer)
->getForm(); ->getForm();
@ -705,6 +710,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testEmptyDataCreatedBeforeTransforming() public function testEmptyDataCreatedBeforeTransforming()
{ {
$form = $this->getBuilder() $form = $this->getBuilder()
->setCompound(false)
->setEmptyData('foo') ->setEmptyData('foo')
->addViewTransformer(new FixedDataTransformer(array( ->addViewTransformer(new FixedDataTransformer(array(
'' => '', '' => '',
@ -722,6 +728,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
{ {
$test = $this; $test = $this;
$form = $this->getBuilder() $form = $this->getBuilder()
->setCompound(false)
->setEmptyData(function ($form) use ($test) { ->setEmptyData(function ($form) use ($test) {
// the form instance is passed to the closure to allow use // the form instance is passed to the closure to allow use
// of form data when creating the empty value // of form data when creating the empty value
@ -795,8 +802,8 @@ class FormTest extends \PHPUnit_Framework_TestCase
->setData('foo') ->setData('foo')
->getForm(); ->getForm();
$form->add($child1 = $this->getBuilder('firstName')->getForm()); $form->add($child1 = $this->getBuilder('firstName')->setCompound(false)->getForm());
$form->add($child2 = $this->getBuilder('lastName')->getForm()); $form->add($child2 = $this->getBuilder('lastName')->setCompound(false)->getForm());
$mapper->expects($this->once()) $mapper->expects($this->once())
->method('mapFormsToData') ->method('mapFormsToData')
@ -812,9 +819,25 @@ class FormTest extends \PHPUnit_Framework_TestCase
)); ));
} }
/*
* https://github.com/symfony/symfony/issues/4480
*/
public function testBindRestoresViewDataIfCompoundAndEmpty()
{
$mapper = $this->getDataMapper();
$object = new \stdClass();
$form = $this->getBuilder('name', null, 'stdClass')
->setDataMapper($mapper)
->setData($object)
->getForm();
$form->bind(array());
$this->assertSame($object, $form->getData());
}
public function testBindMapsBoundChildrenOntoEmptyData() public function testBindMapsBoundChildrenOntoEmptyData()
{ {
$test = $this;
$mapper = $this->getDataMapper(); $mapper = $this->getDataMapper();
$object = new \stdClass(); $object = new \stdClass();
$form = $this->getBuilder() $form = $this->getBuilder()
@ -823,7 +846,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
->setData(null) ->setData(null)
->getForm(); ->getForm();
$form->add($child = $this->getBuilder('name')->getForm()); $form->add($child = $this->getBuilder('name')->setCompound(false)->getForm());
$mapper->expects($this->once()) $mapper->expects($this->once())
->method('mapFormsToData') ->method('mapFormsToData')
@ -839,6 +862,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
$test = $this; $test = $this;
$validator = $this->getFormValidator(); $validator = $this->getFormValidator();
$form = $this->getBuilder() $form = $this->getBuilder()
->setCompound(false)
->addValidator($validator) ->addValidator($validator)
->getForm(); ->getForm();
@ -896,8 +920,8 @@ class FormTest extends \PHPUnit_Framework_TestCase
)); ));
$form = $this->getBuilder('author')->getForm(); $form = $this->getBuilder('author')->getForm();
$form->add($this->getBuilder('name')->getForm()); $form->add($this->getBuilder('name')->setCompound(false)->getForm());
$form->add($this->getBuilder('image')->getForm()); $form->add($this->getBuilder('image')->setCompound(false)->getForm());
$form->bindRequest($request); $form->bindRequest($request);
@ -941,8 +965,8 @@ class FormTest extends \PHPUnit_Framework_TestCase
)); ));
$form = $this->getBuilder('')->getForm(); $form = $this->getBuilder('')->getForm();
$form->add($this->getBuilder('name')->getForm()); $form->add($this->getBuilder('name')->setCompound(false)->getForm());
$form->add($this->getBuilder('image')->getForm()); $form->add($this->getBuilder('image')->setCompound(false)->getForm());
$form->bindRequest($request); $form->bindRequest($request);
@ -981,7 +1005,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
'REQUEST_METHOD' => $method, 'REQUEST_METHOD' => $method,
)); ));
$form = $this->getBuilder('image')->getForm(); $form = $this->getBuilder('image')->setCompound(false)->getForm();
$form->bindRequest($request); $form->bindRequest($request);
@ -1012,7 +1036,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
'REQUEST_METHOD' => $method, 'REQUEST_METHOD' => $method,
)); ));
$form = $this->getBuilder('name')->getForm(); $form = $this->getBuilder('name')->setCompound(false)->getForm();
$form->bindRequest($request); $form->bindRequest($request);
@ -1039,8 +1063,8 @@ class FormTest extends \PHPUnit_Framework_TestCase
)); ));
$form = $this->getBuilder('author')->getForm(); $form = $this->getBuilder('author')->getForm();
$form->add($this->getBuilder('firstName')->getForm()); $form->add($this->getBuilder('firstName')->setCompound(false)->getForm());
$form->add($this->getBuilder('lastName')->getForm()); $form->add($this->getBuilder('lastName')->setCompound(false)->getForm());
$form->bindRequest($request); $form->bindRequest($request);
@ -1065,8 +1089,8 @@ class FormTest extends \PHPUnit_Framework_TestCase
)); ));
$form = $this->getBuilder('')->getForm(); $form = $this->getBuilder('')->getForm();
$form->add($this->getBuilder('firstName')->getForm()); $form->add($this->getBuilder('firstName')->setCompound(false)->getForm());
$form->add($this->getBuilder('lastName')->getForm()); $form->add($this->getBuilder('lastName')->setCompound(false)->getForm());
$form->bindRequest($request); $form->bindRequest($request);
@ -1077,7 +1101,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testBindResetsErrors() public function testBindResetsErrors()
{ {
$form = $this->getBuilder()->getForm(); $form = $this->getBuilder()->setCompound(false)->getForm();
$form->addError(new FormError('Error!')); $form->addError(new FormError('Error!'));
$form->bind('foobar'); $form->bind('foobar');

View File

@ -52,6 +52,11 @@ class UnmodifiableFormConfig implements FormConfigInterface
*/ */
private $virtual; private $virtual;
/**
* @var Boolean
*/
private $compound;
/** /**
* @var array * @var array
*/ */
@ -135,6 +140,7 @@ class UnmodifiableFormConfig implements FormConfigInterface
$this->mapped = $config->getMapped(); $this->mapped = $config->getMapped();
$this->byReference = $config->getByReference(); $this->byReference = $config->getByReference();
$this->virtual = $config->getVirtual(); $this->virtual = $config->getVirtual();
$this->compound = $config->getCompound();
$this->types = $config->getTypes(); $this->types = $config->getTypes();
$this->clientTransformers = $config->getViewTransformers(); $this->clientTransformers = $config->getViewTransformers();
$this->normTransformers = $config->getModelTransformers(); $this->normTransformers = $config->getModelTransformers();
@ -198,6 +204,14 @@ class UnmodifiableFormConfig implements FormConfigInterface
return $this->virtual; return $this->virtual;
} }
/**
* {@inheritdoc}
*/
public function getCompound()
{
return $this->compound;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */