diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index e539b09e9753..3ee840a06b51 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -458,6 +458,28 @@ } } + * Setting the option "property_path" to `false` was deprecated and will be unsupported + as of Symfony 2.3. + + You should use the new option "mapped" instead in order to set that you don't want + a field to be mapped to its parent's data. + + Before: + + ``` + $builder->add('termsAccepted', 'checkbox', array( + 'property_path' => false, + )); + ``` + + After: + + ``` + $builder->add('termsAccepted', 'checkbox', array( + 'mapped' => false, + )); + ``` + ### Validator * The methods `setMessage()`, `getMessageTemplate()` and diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 1e056e978f6c..bc4b136e414b 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -70,3 +70,4 @@ CHANGELOG * deprecated method `guessMinLength` in favor of `guessPattern` * labels don't display field attributes anymore. Label attributes can be passed in the "label_attr" option/variable + * added option "mapped" which should be used instead of setting "property_path" to false diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index f8f2984d4a7d..513f22a2e5f4 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -44,8 +44,9 @@ public function mapDataToForm($data, FormInterface $form) { if (!empty($data)) { $propertyPath = $form->getPropertyPath(); + $config = $form->getConfig(); - if (null !== $propertyPath) { + if (null !== $propertyPath && $config->getMapped()) { $propertyData = $propertyPath->getValue($data); if (is_object($propertyData) && !$form->getAttribute('by_reference')) { @@ -76,8 +77,11 @@ public function mapFormsToData(array $forms, &$data) public function mapFormToData(FormInterface $form, &$data) { $propertyPath = $form->getPropertyPath(); + $config = $form->getConfig(); - if (null !== $propertyPath && $form->isSynchronized() && !$form->isDisabled()) { + // Write-back is disabled if the form is not synchronized (transformation failed) + // and if the form is disabled (modification not allowed) + 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); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 2bed6662378c..5bd3b5e5082f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -44,7 +44,9 @@ public function buildForm(FormBuilder $builder, array $options) ->setDisabled($options['disabled']) ->setErrorBubbling($options['error_bubbling']) ->setEmptyData($options['empty_data']) - ->setPropertyPath($options['property_path']) + // BC compatibility, when "property_path" could be false + ->setPropertyPath(is_string($options['property_path']) ? $options['property_path'] : null) + ->setMapped($options['mapped']) ->setAttribute('read_only', $options['read_only']) ->setAttribute('by_reference', $options['by_reference']) ->setAttribute('error_mapping', $options['error_mapping']) @@ -189,6 +191,11 @@ public function getDefaultOptions() return !$options['single_control']; }; + // BC clause: former property_path=false now equals mapped=false + $mapped = function (Options $options) { + return false !== $options['property_path']; + }; + return array( 'data' => null, 'data_class' => $dataClass, @@ -200,6 +207,7 @@ public function getDefaultOptions() 'max_length' => null, 'pattern' => null, 'property_path' => null, + 'mapped' => $mapped, 'by_reference' => true, 'error_bubbling' => $errorBubbling, 'error_mapping' => array(), diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 2401c61230bb..3525ecd8cc3c 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -168,7 +168,7 @@ public function getName() */ public function getPropertyPath() { - if (!$this->hasParent() || null !== $this->config->getPropertyPath()) { + if (null !== $this->config->getPropertyPath()) { return $this->config->getPropertyPath(); } @@ -176,7 +176,7 @@ public function getPropertyPath() return null; } - if (null === $this->getParent()->getConfig()->getDataClass()) { + if ($this->hasParent() && null === $this->getParent()->getConfig()->getDataClass()) { return new PropertyPath('[' . $this->getName() . ']'); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php index 13e14330c129..d060dfaa0f5f 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php @@ -64,8 +64,14 @@ private function getPropertyPath($path) ->getMock(); } - private function getForm(PropertyPath $propertyPath = null, $byReference, $synchronized = true) + private function getForm(PropertyPath $propertyPath = null, $byReference, $synchronized = true, $mapped = true, $disabled = false) { + $config = $this->getMock('Symfony\Component\Form\FormConfigInterface'); + + $config->expects($this->any()) + ->method('getMapped') + ->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 @@ -76,6 +82,10 @@ private function getForm(PropertyPath $propertyPath = null, $byReference, $synch $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)); @@ -84,6 +94,10 @@ private function getForm(PropertyPath $propertyPath = null, $byReference, $synch ->method('isSynchronized') ->will($this->returnValue($synchronized)); + $form->expects($this->any()) + ->method('isDisabled') + ->will($this->returnValue($disabled)); + return $form; } @@ -132,10 +146,24 @@ public function testMapDataToFormIgnoresEmptyPropertyPath() $form = $this->getForm(null, true); - $form->expects($this->never()) - ->method('setData'); + $this->mapper->mapDataToForm($car, $form); + + $this->assertNull($form->getData()); + } + + public function testMapDataToFormIgnoresUnmapped() + { + $car = new \stdClass(); + $propertyPath = $this->getPropertyPath('engine'); + + $propertyPath->expects($this->never()) + ->method('getValue'); + + $form = $this->getForm($propertyPath, true, true, false); $this->mapper->mapDataToForm($car, $form); + + $this->assertNull($form->getData()); } public function testMapDataToFormIgnoresEmptyData() @@ -143,10 +171,9 @@ public function testMapDataToFormIgnoresEmptyData() $propertyPath = $this->getPropertyPath('engine'); $form = $this->getForm($propertyPath, true); - $form->expects($this->never()) - ->method('setData'); - $this->mapper->mapDataToForm(null, $form); + + $this->assertNull($form->getData()); } public function testMapFormToDataWritesBackIfNotByReference() @@ -201,4 +228,63 @@ public function testMapFormToDataWritesBackIfByReferenceAndReference() $this->mapper->mapFormToData($form, $car); } + + public function testMapFormToDataIgnoresUnmapped() + { + $car = new \stdClass(); + $engine = new \stdClass(); + $propertyPath = $this->getPropertyPath('engine'); + + $propertyPath->expects($this->never()) + ->method('setValue'); + + $form = $this->getForm($propertyPath, true, true, false); + $form->setData($engine); + + $this->mapper->mapFormToData($form, $car); + } + + public function testMapFormToDataIgnoresEmptyData() + { + $car = new \stdClass(); + $propertyPath = $this->getPropertyPath('engine'); + + $propertyPath->expects($this->never()) + ->method('setValue'); + + $form = $this->getForm($propertyPath, true); + $form->setData(null); + + $this->mapper->mapFormToData($form, $car); + } + + public function testMapFormToDataIgnoresUnsynchronized() + { + $car = new \stdClass(); + $engine = new \stdClass(); + $propertyPath = $this->getPropertyPath('engine'); + + $propertyPath->expects($this->never()) + ->method('setValue'); + + $form = $this->getForm($propertyPath, true, false); + $form->setData($engine); + + $this->mapper->mapFormToData($form, $car); + } + + public function testMapFormToDataIgnoresDisabled() + { + $car = new \stdClass(); + $engine = new \stdClass(); + $propertyPath = $this->getPropertyPath('engine'); + + $propertyPath->expects($this->never()) + ->method('setValue'); + + $form = $this->getForm($propertyPath, true, true, true, true); + $form->setData($engine); + + $this->mapper->mapFormToData($form, $car); + } } 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 b575b140d284..8aa7bba87dc2 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -562,4 +562,46 @@ public function testOverrideErrorBubbling() $this->assertTrue($form->getErrorBubbling()); } + + public function testPropertyPath() + { + $form = $this->factory->create('form', null, array( + 'property_path' => 'foo', + )); + + $this->assertEquals(new PropertyPath('foo'), $form->getPropertyPath()); + $this->assertTrue($form->getConfig()->getMapped()); + } + + public function testPropertyPathNullImpliesDefault() + { + $form = $this->factory->createNamed('form', 'name', null, array( + 'property_path' => null, + )); + + $this->assertEquals(new PropertyPath('name'), $form->getPropertyPath()); + $this->assertTrue($form->getConfig()->getMapped()); + } + + // BC + public function testPropertyPathFalseImpliesDefaultNotMapped() + { + $form = $this->factory->createNamed('form', 'name', null, array( + 'property_path' => false, + )); + + $this->assertEquals(new PropertyPath('name'), $form->getPropertyPath()); + $this->assertFalse($form->getConfig()->getMapped()); + } + + public function testNotMapped() + { + $form = $this->factory->create('form', null, array( + 'property_path' => 'foo', + 'mapped' => false, + )); + + $this->assertEquals(new PropertyPath('foo'), $form->getPropertyPath()); + $this->assertFalse($form->getConfig()->getMapped()); + } }