diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index 657d9d63bec2..bc31505157f7 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -13,6 +13,8 @@ use Symfony\Component\Form\DataMapperInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\PropertyAccess\Exception\AccessException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -46,7 +48,7 @@ public function mapDataToForms($data, $forms) $config = $form->getConfig(); if (!$empty && null !== $propertyPath && $config->getMapped()) { - $form->setData($this->propertyAccessor->getValue($data, $propertyPath)); + $form->setData($this->getPropertyValue($data, $propertyPath)); } else { $form->setData($config->getData()); } @@ -76,16 +78,32 @@ public function mapFormsToData($forms, &$data) $propertyValue = $form->getData(); // If the field is of type DateTimeInterface and the data is the same skip the update to // keep the original object hash - if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->propertyAccessor->getValue($data, $propertyPath)) { + if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->getPropertyValue($data, $propertyPath)) { continue; } // If the data is identical to the value in $data, we are // dealing with a reference - if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->propertyAccessor->getValue($data, $propertyPath)) { + if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->getPropertyValue($data, $propertyPath)) { $this->propertyAccessor->setValue($data, $propertyPath, $propertyValue); } } } } + + private function getPropertyValue($data, $propertyPath) + { + try { + return $this->propertyAccessor->getValue($data, $propertyPath); + } catch (AccessException $e) { + if (!$e instanceof UninitializedPropertyException + // For versions without UninitializedPropertyException check the exception message + && (class_exists(UninitializedPropertyException::class) || false === strpos($e->getMessage(), 'You should initialize it')) + ) { + throw $e; + } + + return null; + } + } } 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 da351295c381..8f274ad92905 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormConfigBuilder; +use Symfony\Component\Form\Tests\Fixtures\TypehintedPropertiesCar; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyPath; @@ -113,6 +114,23 @@ public function testMapDataToFormsIgnoresUnmapped() $this->assertNull($form->getData()); } + /** + * @requires PHP 7.4 + */ + public function testMapDataToFormsIgnoresUninitializedProperties() + { + $engineForm = new Form(new FormConfigBuilder('engine', null, $this->dispatcher)); + $colorForm = new Form(new FormConfigBuilder('color', null, $this->dispatcher)); + + $car = new TypehintedPropertiesCar(); + $car->engine = 'BMW'; + + $this->mapper->mapDataToForms($car, [$engineForm, $colorForm]); + + $this->assertSame($car->engine, $engineForm->getData()); + $this->assertNull($colorForm->getData()); + } + public function testMapDataToFormsSetsDefaultDataIfPassedDataIsNull() { $default = new \stdClass(); @@ -293,13 +311,28 @@ public function testMapFormsToDataIgnoresDisabled() $config->setPropertyPath($propertyPath); $config->setData($engine); $config->setDisabled(true); - $form = new Form($config); + $form = new SubmittedForm($config); $this->mapper->mapFormsToData([$form], $car); $this->assertSame($initialEngine, $car->engine); } + /** + * @requires PHP 7.4 + */ + public function testMapFormsToUninitializedProperties() + { + $car = new TypehintedPropertiesCar(); + $config = new FormConfigBuilder('engine', null, $this->dispatcher); + $config->setData('BMW'); + $form = new SubmittedForm($config); + + $this->mapper->mapFormsToData([$form], $car); + + $this->assertSame('BMW', $car->engine); + } + /** * @dataProvider provideDate */ @@ -339,7 +372,7 @@ public function isSubmitted() } } -class NotSynchronizedForm extends Form +class NotSynchronizedForm extends SubmittedForm { public function isSynchronized() { diff --git a/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php b/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php new file mode 100644 index 000000000000..6d88c4841ab3 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Fixtures; + +class TypehintedPropertiesCar +{ + public ?string $engine; + public ?string $color; +}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: