diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index 71f54c5fba8e..96078ce20b58 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -24,7 +24,7 @@ class PropertyPathMapper implements DataMapperInterface public function mapDataToForms($data, array $forms) { 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)) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 14d2203e982c..8adbf1180b9d 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -42,6 +42,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->setMapped($options['mapped']) ->setByReference($options['by_reference']) ->setVirtual($options['virtual']) + ->setCompound($options['compound']) ->setData($options['data']) ->setDataMapper(new PropertyPathMapper()) ; @@ -116,7 +117,7 @@ public function buildView(FormViewInterface $view, FormInterface $form, array $o 'multipart' => false, 'attr' => $options['attr'], 'label_attr' => $options['label_attr'], - 'compound' => $options['compound'], + 'compound' => $form->getConfig()->getCompound(), 'types' => $types, 'translation_domain' => $translationDomain, )); @@ -160,7 +161,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) } return function (FormInterface $form) { - return count($form) > 0 ? array() : ''; + return $form->getConfig()->getCompound() ? array() : ''; }; }; diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index e40b4f89324e..83b92b848374 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -345,7 +345,7 @@ public function setData($modelData) $viewData = $this->normToView($normData); // Validate if view data matches data class (unless empty) - if (!empty($viewData)) { + if ('' !== $viewData && null !== $viewData) { $dataClass = $this->config->getDataClass(); $actualType = is_object($viewData) ? 'an instance of class ' . get_class($viewData) : ' a(n) ' . gettype($viewData); @@ -378,7 +378,7 @@ public function setData($modelData) $this->viewData = $viewData; $this->synchronized = true; - if (count($this->children) > 0 && $this->config->getDataMapper()) { + if ($this->config->getCompound() && $this->config->getDataMapper()) { // Update child forms from the data $this->config->getDataMapper()->mapDataToForms($viewData, $this->children); } @@ -477,37 +477,42 @@ public function bind($submittedData) $this->config->getEventDispatcher()->dispatch(FormEvents::BIND_CLIENT_DATA, $event); $submittedData = $event->getData(); - // Build the data in the view format - $viewData = $submittedData; - - if (count($this->children) > 0) { - if (null === $viewData || '' === $viewData) { - $viewData = array(); + // Check whether the form is compound. + // This check is preferrable over checking the number of children, + // since forms without children may also be compound. + // (think of empty collection forms) + if ($this->config->getCompound()) { + if (null === $submittedData || '' === $submittedData) { + $submittedData = array(); } - if (!is_array($viewData)) { - throw new UnexpectedTypeException($viewData, 'array'); + if (!is_array($submittedData)) { + throw new UnexpectedTypeException($submittedData, 'array'); } foreach ($this->children as $name => $child) { - if (!isset($viewData[$name])) { - $viewData[$name] = null; + if (!isset($submittedData[$name])) { + $submittedData[$name] = null; } } - foreach ($viewData as $name => $value) { + foreach ($submittedData as $name => $value) { if ($this->has($name)) { $this->children[$name]->bind($value); } else { $extraData[$name] = $value; } } + } - // If we have a data mapper, use old view data and merge - // data from the children into it later - if ($this->config->getDataMapper()) { - $viewData = $this->getViewData(); - } + // By default, the submitted data is also the data in view format + $viewData = $submittedData; + + // 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) { @@ -522,7 +527,7 @@ public function bind($submittedData) } // 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); } @@ -542,7 +547,6 @@ public function bind($submittedData) $this->config->getEventDispatcher()->dispatch(FormEvents::BIND_NORM_DATA, $event); $normData = $event->getData(); - // Synchronize representations - must not change the content! $modelData = $this->normToModel($normData); $viewData = $this->normToView($normData); @@ -591,7 +595,7 @@ public function bindRequest(Request $request) // Form bound without name $params = $request->request->all(); $files = $request->files->all(); - } elseif (count($this->children) > 0) { + } elseif ($this->config->getCompound()) { // Form bound with name and children $params = $request->request->get($name, array()); $files = $request->files->get($name, array()); @@ -841,6 +845,10 @@ public function add(FormInterface $child) 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; $child->setParent($this); diff --git a/src/Symfony/Component/Form/FormConfig.php b/src/Symfony/Component/Form/FormConfig.php index ee173695ae29..76a690da59fa 100644 --- a/src/Symfony/Component/Form/FormConfig.php +++ b/src/Symfony/Component/Form/FormConfig.php @@ -53,6 +53,11 @@ class FormConfig implements FormConfigEditorInterface */ private $virtual = false; + /** + * @var Boolean + */ + private $compound = true; + /** * @var array */ @@ -356,6 +361,14 @@ public function getVirtual() return $this->virtual; } + /** + * {@inheritdoc} + */ + public function getCompound() + { + return $this->compound; + } + /** * {@inheritdoc} */ @@ -632,6 +645,16 @@ public function setVirtual($virtual) return $this; } + /** + * {@inheritdoc} + */ + public function setCompound($compound) + { + $this->compound = $compound; + + return $this; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/FormConfigEditorInterface.php b/src/Symfony/Component/Form/FormConfigEditorInterface.php index 0176d7f81418..10c1e04d0f14 100644 --- a/src/Symfony/Component/Form/FormConfigEditorInterface.php +++ b/src/Symfony/Component/Form/FormConfigEditorInterface.php @@ -199,6 +199,17 @@ function setByReference($byReference); */ 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. * diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index d656ef32016b..22354d8fc19f 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -65,6 +65,17 @@ function getByReference(); */ 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. * diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index eb5e99b3cc84..2ad120297c1e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -18,7 +18,7 @@ class CollectionTypeTest extends TypeTestCase public function testContainsNoChildByDefault() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', )); $this->assertCount(0, $form); @@ -27,7 +27,7 @@ public function testContainsNoChildByDefault() public function testSetDataAdjustsSize() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', 'options' => array( 'max_length' => 20, ), @@ -53,7 +53,7 @@ public function testSetDataAdjustsSize() public function testThrowsExceptionIfObjectIsNotTraversable() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', )); $this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException'); $form->setData(new \stdClass()); @@ -62,7 +62,7 @@ public function testThrowsExceptionIfObjectIsNotTraversable() public function testNotResizedIfBoundWithMissingData() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', )); $form->setData(array('foo@foo.com', 'bar@bar.com')); $form->bind(array('foo@bar.com')); @@ -70,13 +70,13 @@ public function testNotResizedIfBoundWithMissingData() $this->assertTrue($form->has('0')); $this->assertTrue($form->has('1')); $this->assertEquals('foo@bar.com', $form[0]->getData()); - $this->assertNull($form[1]->getData()); + $this->assertEquals('', $form[1]->getData()); } public function testResizedDownIfBoundWithMissingDataAndAllowDelete() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', 'allow_delete' => true, )); $form->setData(array('foo@foo.com', 'bar@bar.com')); @@ -91,7 +91,7 @@ public function testResizedDownIfBoundWithMissingDataAndAllowDelete() public function testNotResizedIfBoundWithExtraData() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', )); $form->setData(array('foo@bar.com')); $form->bind(array('foo@foo.com', 'bar@bar.com')); @@ -104,7 +104,7 @@ public function testNotResizedIfBoundWithExtraData() public function testResizedUpIfBoundWithExtraDataAndAllowAdd() { $form = $this->factory->create('collection', null, array( - 'type' => 'form', + 'type' => 'text', 'allow_add' => true, )); $form->setData(array('foo@bar.com')); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 03cce4cdc620..d9d8866be4b1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -77,6 +77,7 @@ public function testBoundDataIsTrimmedBeforeTransforming() null => '', 'reverse[a]' => 'a', ))) + ->setCompound(false) ->getForm(); $form->bind(' a '); @@ -92,6 +93,7 @@ public function testBoundDataIsNotTrimmedBeforeTransformingIfNoTrimming() null => '', 'reverse[ a ]' => ' a ', ))) + ->setCompound(false) ->getForm(); $form->bind(' a '); @@ -235,8 +237,8 @@ public function testBindWithEmptyDataCreatesObjectIfClassAvailable() 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'required' => false, )); - $form->add($this->factory->createNamed('firstName', 'form')); - $form->add($this->factory->createNamed('lastName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); + $form->add($this->factory->createNamed('lastName', 'text')); $form->setData(null); // partially empty, still an object is created @@ -256,8 +258,8 @@ public function testBindWithEmptyDataCreatesObjectIfInitiallyBoundWithObject() 'data' => new Author(), 'required' => false, )); - $form->add($this->factory->createNamed('firstName', 'form')); - $form->add($this->factory->createNamed('lastName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); + $form->add($this->factory->createNamed('lastName', 'text')); $form->setData(null); // partially empty, still an object is created @@ -276,7 +278,7 @@ public function testBindWithEmptyDataCreatesArrayIfDataClassIsNull() 'data_class' => null, 'required' => false, )); - $form->add($this->factory->createNamed('firstName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); $form->setData(null); $form->bind(array('firstName' => 'Bernhard')); @@ -290,8 +292,8 @@ public function testBindEmptyWithEmptyDataCreatesNoObjectIfNotRequired() 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'required' => false, )); - $form->add($this->factory->createNamed('firstName', 'form')); - $form->add($this->factory->createNamed('lastName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); + $form->add($this->factory->createNamed('lastName', 'text')); $form->setData(null); $form->bind(array('firstName' => '', 'lastName' => '')); @@ -305,8 +307,8 @@ public function testBindEmptyWithEmptyDataCreatesObjectIfRequired() 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'required' => true, )); - $form->add($this->factory->createNamed('firstName', 'form')); - $form->add($this->factory->createNamed('lastName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); + $form->add($this->factory->createNamed('lastName', 'text')); $form->setData(null); $form->bind(array('firstName' => '', 'lastName' => '')); @@ -320,7 +322,7 @@ public function testBindEmptyWithEmptyDataCreatesObjectIfRequired() public function testBindWithEmptyDataStoresArrayIfNoClassAvailable() { $form = $this->factory->create('form'); - $form->add($this->factory->createNamed('firstName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); $form->setData(null); $form->bind(array('firstName' => 'Bernhard')); @@ -328,7 +330,7 @@ public function testBindWithEmptyDataStoresArrayIfNoClassAvailable() $this->assertSame(array('firstName' => 'Bernhard'), $form->getData()); } - public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNoChildren() + public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNotCompound() { $form = $this->factory->createBuilder('form') ->addViewTransformer(new FixedDataTransformer(array( @@ -337,6 +339,7 @@ public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNoChildren( // required to test that bind(null) is converted to '' 'empty' => '', ))) + ->setCompound(false) ->getForm(); $form->bind(null); @@ -352,7 +355,7 @@ public function testBindWithEmptyDataUsesEmptyDataOption() 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'empty_data' => $author, )); - $form->add($this->factory->createNamed('firstName', 'form')); + $form->add($this->factory->createNamed('firstName', 'text')); $form->bind(array('firstName' => 'Bernhard')); @@ -360,34 +363,31 @@ public function testBindWithEmptyDataUsesEmptyDataOption() $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 */ - public function testSetDataThroughParamsWithZero() + public function testSetDataThroughParamsWithZero($data, $dataAsString) { - $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' => '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')); + $form = $this->factory->create('form', null, array( + 'data' => $data, + 'compound' => false, + )); $view = $form->createView(); $this->assertFalse($form->isEmpty()); - $this->assertSame('00000', $view->getVar('value')); - $this->assertSame('00000', $form->getData()); + $this->assertSame($dataAsString, $view->getVar('value')); + $this->assertSame($dataAsString, $form->getData()); } /** @@ -412,14 +412,14 @@ public function testSubformDoesntCallSetters() $builder->add('reference', 'form', array( 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', )); - $builder->get('reference')->add('firstName', 'form'); + $builder->get('reference')->add('firstName', 'text'); $form = $builder->getForm(); $form->bind(array( - // reference has a getter, but not setter + // reference has a getter, but not setter 'reference' => array( 'firstName' => 'Foo', - ) + ) )); $this->assertEquals('Foo', $author->getReference()->firstName); @@ -435,7 +435,7 @@ public function testSubformCallsSettersIfTheObjectChanged() $builder->add('referenceCopy', 'form', array( 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', )); - $builder->get('referenceCopy')->add('firstName', 'form'); + $builder->get('referenceCopy')->add('firstName', 'text'); $form = $builder->getForm(); $form['referenceCopy']->setData($newReference); // new author object @@ -459,7 +459,7 @@ public function testSubformCallsSettersIfByReferenceIsFalse() 'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author', 'by_reference' => false )); - $builder->get('referenceCopy')->add('firstName', 'form'); + $builder->get('referenceCopy')->add('firstName', 'text'); $form = $builder->getForm(); $form->bind(array( diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php index b324a49150a4..d99313206bc9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php @@ -21,7 +21,7 @@ protected function setUp() parent::setUp(); $this->form = $this->factory->create('repeated', null, array( - 'type' => 'form', + 'type' => 'text', )); $this->form->setData(null); } @@ -37,7 +37,7 @@ public function testSetData() public function testSetOptions() { $form = $this->factory->create('repeated', null, array( - 'type' => 'form', + 'type' => 'text', 'options' => array('label' => 'Global'), )); @@ -51,7 +51,7 @@ public function testSetOptionsPerChild() { $form = $this->factory->create('repeated', null, array( // the global required value cannot be overriden - 'type' => 'form', + 'type' => 'text', 'first_options' => array('label' => 'Test', 'required' => false), 'second_options' => array('label' => 'Test2') )); @@ -66,7 +66,7 @@ public function testSetRequired() { $form = $this->factory->create('repeated', null, array( 'required' => false, - 'type' => 'form', + 'type' => 'text', )); $this->assertFalse($form['first']->isRequired()); @@ -76,7 +76,7 @@ public function testSetRequired() public function testSetOptionsPerChildAndOverwrite() { $form = $this->factory->create('repeated', null, array( - 'type' => 'form', + 'type' => 'text', 'options' => array('label' => 'Label'), 'second_options' => array('label' => 'Second label') )); diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index 8470d207530e..7f6408c689b3 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -148,12 +148,14 @@ public function testValidateTokenOnBindIfRootAndCompound($valid) ->will($this->returnValue($valid)); $form = $this->factory - ->create('form', null, array( + ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', 'csrf_provider' => $this->csrfProvider, 'intention' => '%INTENTION%', 'compound' => true, - )); + )) + ->add('child', 'text') + ->getForm(); $form->bind(array( 'child' => 'foobar', @@ -173,12 +175,14 @@ public function testFailIfRootAndCompoundAndTokenMissing() ->method('isCsrfTokenValid'); $form = $this->factory - ->create('form', null, array( + ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', 'csrf_provider' => $this->csrfProvider, 'intention' => '%INTENTION%', 'compound' => true, - )); + )) + ->add('child', 'text') + ->getForm(); $form->bind(array( 'child' => 'foobar', diff --git a/src/Symfony/Component/Form/Tests/FormTest.php b/src/Symfony/Component/Form/Tests/FormTest.php index 63dc7c5a6b12..cff810c2836f 100644 --- a/src/Symfony/Component/Form/Tests/FormTest.php +++ b/src/Symfony/Component/Form/Tests/FormTest.php @@ -31,8 +31,6 @@ class FormTest extends \PHPUnit_Framework_TestCase private $factory; - private $builder; - private $form; protected function setUp() @@ -279,14 +277,15 @@ public function testNotEmptyIfChildNotEmpty() 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() { - $form = $this->getBuilder()->setDisabled(true)->getForm(); + $form = $this->getBuilder()->setCompound(false)->setDisabled(true)->getForm(); $form->bind('foobar'); $this->assertTrue($form->isValid()); @@ -318,10 +317,11 @@ public function testNotValidIfNotBound() public function testNotValidIfErrors() { - $this->form->bind('foobar'); - $this->form->addError(new FormError('Error!')); + $form = $this->getBuilder()->setCompound(false)->getForm(); + $form->bind('foobar'); + $form->addError(new FormError('Error!')); - $this->assertFalse($this->form->isValid()); + $this->assertFalse($form->isValid()); } public function testNotValidIfChildNotValid() @@ -403,7 +403,7 @@ public function testRemove() */ 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->remove('foo'); } @@ -445,9 +445,10 @@ public function testIterator() 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() @@ -586,7 +587,7 @@ public function testSetDataConvertsScalarToStringIfOnlyModelTransformer() */ public function testSetDataConvertsNullToStringIfNoTransformer() { - $form = $this->getBuilder()->getForm(); + $form = $this->getBuilder()->setCompound(false)->getForm(); $form->setData(null); @@ -597,7 +598,7 @@ public function testSetDataConvertsNullToStringIfNoTransformer() public function testBindConvertsEmptyToNullIfNoTransformer() { - $form = $this->getBuilder()->getForm(); + $form = $this->getBuilder()->setCompound(false)->getForm(); $form->bind(''); @@ -641,6 +642,7 @@ public function testBindExecutesTransformationChain() public function testBindExecutesViewTransformersInReverseOrder() { $form = $this->getBuilder() + ->setCompound(false) ->addViewTransformer(new FixedDataTransformer(array( '' => '', 'third' => 'second', @@ -659,6 +661,7 @@ public function testBindExecutesViewTransformersInReverseOrder() public function testBindExecutesModelTransformersInOrder() { $form = $this->getBuilder() + ->setCompound(false) ->addModelTransformer(new FixedDataTransformer(array( '' => '', 'second' => 'first', @@ -681,9 +684,10 @@ public function testSynchronizedByDefault() 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() @@ -694,6 +698,7 @@ public function testNotSynchronizedIfTransformationFailed() ->will($this->throwException(new TransformationFailedException())); $form = $this->getBuilder() + ->setCompound(false) ->addViewTransformer($transformer) ->getForm(); @@ -705,6 +710,7 @@ public function testNotSynchronizedIfTransformationFailed() public function testEmptyDataCreatedBeforeTransforming() { $form = $this->getBuilder() + ->setCompound(false) ->setEmptyData('foo') ->addViewTransformer(new FixedDataTransformer(array( '' => '', @@ -722,6 +728,7 @@ public function testEmptyDataFromClosure() { $test = $this; $form = $this->getBuilder() + ->setCompound(false) ->setEmptyData(function ($form) use ($test) { // the form instance is passed to the closure to allow use // of form data when creating the empty value @@ -795,8 +802,8 @@ public function testBindMapsBoundChildrenOntoExistingClientData() ->setData('foo') ->getForm(); - $form->add($child1 = $this->getBuilder('firstName')->getForm()); - $form->add($child2 = $this->getBuilder('lastName')->getForm()); + $form->add($child1 = $this->getBuilder('firstName')->setCompound(false)->getForm()); + $form->add($child2 = $this->getBuilder('lastName')->setCompound(false)->getForm()); $mapper->expects($this->once()) ->method('mapFormsToData') @@ -812,9 +819,25 @@ public function testBindMapsBoundChildrenOntoExistingClientData() )); } + /* + * 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() { - $test = $this; $mapper = $this->getDataMapper(); $object = new \stdClass(); $form = $this->getBuilder() @@ -823,7 +846,7 @@ public function testBindMapsBoundChildrenOntoEmptyData() ->setData(null) ->getForm(); - $form->add($child = $this->getBuilder('name')->getForm()); + $form->add($child = $this->getBuilder('name')->setCompound(false)->getForm()); $mapper->expects($this->once()) ->method('mapFormsToData') @@ -839,6 +862,7 @@ public function testBindValidatesAfterTransformation() $test = $this; $validator = $this->getFormValidator(); $form = $this->getBuilder() + ->setCompound(false) ->addValidator($validator) ->getForm(); @@ -896,8 +920,8 @@ public function testBindPostOrPutRequest($method) )); $form = $this->getBuilder('author')->getForm(); - $form->add($this->getBuilder('name')->getForm()); - $form->add($this->getBuilder('image')->getForm()); + $form->add($this->getBuilder('name')->setCompound(false)->getForm()); + $form->add($this->getBuilder('image')->setCompound(false)->getForm()); $form->bindRequest($request); @@ -941,8 +965,8 @@ public function testBindPostOrPutRequestWithEmptyRootFormName($method) )); $form = $this->getBuilder('')->getForm(); - $form->add($this->getBuilder('name')->getForm()); - $form->add($this->getBuilder('image')->getForm()); + $form->add($this->getBuilder('name')->setCompound(false)->getForm()); + $form->add($this->getBuilder('image')->setCompound(false)->getForm()); $form->bindRequest($request); @@ -981,7 +1005,7 @@ public function testBindPostOrPutRequestWithSingleChildForm($method) 'REQUEST_METHOD' => $method, )); - $form = $this->getBuilder('image')->getForm(); + $form = $this->getBuilder('image')->setCompound(false)->getForm(); $form->bindRequest($request); @@ -1012,7 +1036,7 @@ public function testBindPostOrPutRequestWithSingleChildFormUploadedFile($method) 'REQUEST_METHOD' => $method, )); - $form = $this->getBuilder('name')->getForm(); + $form = $this->getBuilder('name')->setCompound(false)->getForm(); $form->bindRequest($request); @@ -1039,8 +1063,8 @@ public function testBindGetRequest() )); $form = $this->getBuilder('author')->getForm(); - $form->add($this->getBuilder('firstName')->getForm()); - $form->add($this->getBuilder('lastName')->getForm()); + $form->add($this->getBuilder('firstName')->setCompound(false)->getForm()); + $form->add($this->getBuilder('lastName')->setCompound(false)->getForm()); $form->bindRequest($request); @@ -1065,8 +1089,8 @@ public function testBindGetRequestWithEmptyRootFormName() )); $form = $this->getBuilder('')->getForm(); - $form->add($this->getBuilder('firstName')->getForm()); - $form->add($this->getBuilder('lastName')->getForm()); + $form->add($this->getBuilder('firstName')->setCompound(false)->getForm()); + $form->add($this->getBuilder('lastName')->setCompound(false)->getForm()); $form->bindRequest($request); @@ -1077,7 +1101,7 @@ public function testBindGetRequestWithEmptyRootFormName() public function testBindResetsErrors() { - $form = $this->getBuilder()->getForm(); + $form = $this->getBuilder()->setCompound(false)->getForm(); $form->addError(new FormError('Error!')); $form->bind('foobar'); diff --git a/src/Symfony/Component/Form/UnmodifiableFormConfig.php b/src/Symfony/Component/Form/UnmodifiableFormConfig.php index 27bc13044a7f..57bfec6e4cd9 100644 --- a/src/Symfony/Component/Form/UnmodifiableFormConfig.php +++ b/src/Symfony/Component/Form/UnmodifiableFormConfig.php @@ -52,6 +52,11 @@ class UnmodifiableFormConfig implements FormConfigInterface */ private $virtual; + /** + * @var Boolean + */ + private $compound; + /** * @var array */ @@ -135,6 +140,7 @@ public function __construct(FormConfigInterface $config) $this->mapped = $config->getMapped(); $this->byReference = $config->getByReference(); $this->virtual = $config->getVirtual(); + $this->compound = $config->getCompound(); $this->types = $config->getTypes(); $this->clientTransformers = $config->getViewTransformers(); $this->normTransformers = $config->getModelTransformers(); @@ -198,6 +204,14 @@ public function getVirtual() return $this->virtual; } + /** + * {@inheritdoc} + */ + public function getCompound() + { + return $this->compound; + } + /** * {@inheritdoc} */