diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 9dfa8af2717c7..cdc011be9ed28 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -41,6 +41,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->setByReference($options['by_reference']) ->setVirtual($options['virtual']) ->setCompound($options['compound']) + ->setIgnoreMissing($options['ignore_missing']) ->setData(isset($options['data']) ? $options['data'] : null) ->setDataLocked(isset($options['data'])) ->setDataMapper($options['compound'] ? new PropertyPathMapper() : null) @@ -125,6 +126,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) 'attr' => $options['attr'], 'label_attr' => $options['label_attr'], 'compound' => $form->getConfig()->getCompound(), + 'ignore_missing' => $form->getConfig()->getIgnoreMissing(), 'block_prefixes' => $blockPrefixes, 'unique_block_prefix' => $uniqueBlockPrefix, 'translation_domain' => $translationDomain, @@ -216,6 +218,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) 'label_attr' => array(), 'virtual' => false, 'compound' => true, + 'ignore_missing' => null, 'translation_domain' => null, )); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index ec3d5393a8382..979d7dc9a80ab 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -180,6 +180,15 @@ public function getName() return $this->config->getName(); } + public function getIgnoreMissing() + { + if ($this->parent && null === $this->config->getIgnoreMissing()) { + return $this->parent->getIgnoreMissing(); + } + + return $this->config->getIgnoreMissing(); + } + /** * {@inheritdoc} */ @@ -509,6 +518,8 @@ public function bind($submittedData) // and radio buttons with empty values. if (is_scalar($submittedData)) { $submittedData = (string) $submittedData; + } elseif ($this->getIgnoreMissing() && null === $submittedData) { + $submittedData = $this->getViewData(); } // Initialize errors in the very beginning so that we don't lose any diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index 9a21df464212d..8ddf84b08829f 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -66,6 +66,11 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $compound = false; + /** + * @var Boolean + */ + private $ignoreMissing; + /** * @var ResolvedFormTypeInterface */ @@ -445,6 +450,14 @@ public function getCompound() return $this->compound; } + /** + * {@inheritdoc} + */ + public function getIgnoreMissing() + { + return $this->ignoreMissing; + } + /** * {@inheritdoc} */ @@ -787,6 +800,20 @@ public function setCompound($compound) return $this; } + /** + * {@inheritdoc} + */ + public function setIgnoreMissing($ignoreMissing) + { + if ($this->locked) { + throw new FormException('The config builder cannot be modified anymore.'); + } + + $this->ignoreMissing = $ignoreMissing; + + return $this; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php index 35eff8790bd29..bccf58ec51158 100644 --- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php @@ -210,6 +210,17 @@ public function setVirtual($virtual); */ public function setCompound($compound); + /** + * Sets whether the form should support partial binding. + * + * @param Boolean $ignoreMissing Whether the form should support partial binding. + * + * @return self The configuration object. + * + * @see FormConfigInterface::getPartial() + */ + public function setIgnoreMissing($ignoreMissing); + /** * Set the types. * diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index 364d6a60b0a09..d89074df5607f 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -76,6 +76,16 @@ public function getVirtual(); */ public function getCompound(); + /** + * Returns whether the form explicitly supports missing children + * + * When enabled, missing child data uses pre-existing model data + * rather than defaulting to `null`. + * + * @return null|Boolean Whether the form explicitly supports missing children + */ + public function getIgnoreMissing(); + /** * Returns the form types used to construct the form. * diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 3a590b7509af3..cfc1c3540e5b2 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -89,6 +89,63 @@ public function testBindIsIgnoredIfDisabled() $this->assertTrue($form->isBound()); } + public function testBindUsesViewDataIfNullAndIgnoreMissing() + { + $form = $this->getBuilder('name', new EventDispatcher()) + ->setIgnoreMissing(true) + ->addViewTransformer(new FixedDataTransformer(array( + '' => '', + 'norm' => 'client', + ))) + ->addModelTransformer(new FixedDataTransformer(array( + '' => '', + 'app' => 'norm', + ))) + ->getForm() + ; + + $form->setData('app'); + + $form->bind(null); + + $this->assertEquals('app', $form->getData()); + $this->assertEquals('norm', $form->getNormData()); + $this->assertEquals('client', $form->getClientData()); + } + + public function testIgnoreMissingUsesParentSettingByDefault() + { + $parent = $this->getBuilder()->setIgnoreMissing(true)->getForm(); + $child = $this->getBuilder()->getForm(); + + $child->setParent($parent); + + $this->assertTrue($child->getIgnoreMissing()); + } + + public function testIgnoreMissingDefaultsToNull() + { + $form = $this->getBuilder()->getForm(); + $this->assertNull($form->getIgnoreMissing()); + } + + public function testIgnoreMissingUsesSetValue() + { + $parent = $this->getBuilder()->setIgnoreMissing(false)->getForm(); + $child = $this->getBuilder()->setIgnoreMissing(true)->getForm(); + + $child->setParent($parent); + + $this->assertTrue($child->getIgnoreMissing()); + + $parent = $this->getBuilder()->setIgnoreMissing(true)->getForm(); + $child = $this->getBuilder()->setIgnoreMissing(false)->getForm(); + + $child->setParent($parent); + + $this->assertFalse($child->getIgnoreMissing()); + } + public function testNeverRequiredIfParentNotRequired() { $parent = $this->getBuilder()->setRequired(false)->getForm();