diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index 657d9d63bec26..ffbd365898152 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,12 @@ public function mapDataToForms($data, $forms) $config = $form->getConfig(); if (!$empty && null !== $propertyPath && $config->getMapped()) { - $form->setData($this->propertyAccessor->getValue($data, $propertyPath)); + try { + $form->setData($this->propertyAccessor->getValue($data, $propertyPath)); + } catch (AccessException $e) { + // Skip unitialized properties on $data + $this->catchUninitializedPropertyException($e); + } } else { $form->setData($config->getData()); } @@ -76,16 +83,54 @@ 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); } } } } + + /** + * Get the property value per PropertyAccessor. + * Treat uninitialized properties as null. + * + * @param object|array $objectOrArray The object or array to traverse + * @param string|PropertyPathInterface $propertyPath The property path to read + * + * @return mixed The value at the end of the property path + * + * @throws Exception\InvalidArgumentException If the property path is invalid + * @throws Exception\UnexpectedTypeException If a value within the path is neither object nor array + */ + private function getPropertyValue($data, $propertyPath) + { + try { + return $this->propertyAccessor->getValue($data, $propertyPath); + } catch (AccessException $e) { + // The following line might be removed in future versions + // See https://github.com/symfony/symfony/issues/36754 + $this->catchUninitializedPropertyException($e); + + return null; + } + } + + /** + * Throw everything but UninitializedPropertyException. + */ + private function catchUninitializedPropertyException(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; + } + } } 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 da351295c381e..f98beecb3f229 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,22 @@ public function testMapDataToFormsIgnoresUnmapped() $this->assertNull($form->getData()); } + /** + * @requires PHP 7.4 + */ + public function testMapDataToFormsIgnoresUninitializedProperties() + { + $engineForm = new Form(new FormConfigBuilder('engine', \stdClass::class, $this->dispatcher)); + $colorForm = new Form(new FormConfigBuilder('color', \stdClass::class, $this->dispatcher)); + + $car = new TypehintedPropertiesCar(); + $car->engine = new \stdClass(); + + $this->mapper->mapDataToForms($car, [$engineForm, $colorForm]); + + $this->assertSame($car->engine, $engineForm->getData()); + } + public function testMapDataToFormsSetsDefaultDataIfPassedDataIsNull() { $default = new \stdClass(); @@ -293,13 +310,29 @@ 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() + { + $engine = new \stdClass(); + $car = new TypehintedPropertiesCar(); + $config = new FormConfigBuilder('engine', \stdClass::class, $this->dispatcher); + $config->setData($engine); + $form = new SubmittedForm($config); + + $this->mapper->mapFormsToData([$form], $car); + + $this->assertSame($engine, $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 0000000000000..df1ebe2c9bf1f --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/TypehintedPropertiesCar.php @@ -0,0 +1,8 @@ + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy