Skip to content

Commit a0ceb81

Browse files
ph-fritschexabbuh
authored andcommitted
silently ignore uninitialized properties when mapping data to forms
1 parent 13982b5 commit a0ceb81

File tree

3 files changed

+74
-5
lines changed

3 files changed

+74
-5
lines changed

src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php

+21-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\Form\DataMapperInterface;
1515
use Symfony\Component\Form\Exception\UnexpectedTypeException;
16+
use Symfony\Component\PropertyAccess\Exception\AccessException;
17+
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
1618
use Symfony\Component\PropertyAccess\PropertyAccess;
1719
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1820

@@ -46,7 +48,7 @@ public function mapDataToForms($data, $forms)
4648
$config = $form->getConfig();
4749

4850
if (!$empty && null !== $propertyPath && $config->getMapped()) {
49-
$form->setData($this->propertyAccessor->getValue($data, $propertyPath));
51+
$form->setData($this->getPropertyValue($data, $propertyPath));
5052
} else {
5153
$form->setData($config->getData());
5254
}
@@ -76,16 +78,32 @@ public function mapFormsToData($forms, &$data)
7678
$propertyValue = $form->getData();
7779
// If the field is of type DateTimeInterface and the data is the same skip the update to
7880
// keep the original object hash
79-
if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->propertyAccessor->getValue($data, $propertyPath)) {
81+
if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->getPropertyValue($data, $propertyPath)) {
8082
continue;
8183
}
8284

8385
// If the data is identical to the value in $data, we are
8486
// dealing with a reference
85-
if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->propertyAccessor->getValue($data, $propertyPath)) {
87+
if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->getPropertyValue($data, $propertyPath)) {
8688
$this->propertyAccessor->setValue($data, $propertyPath, $propertyValue);
8789
}
8890
}
8991
}
9092
}
93+
94+
private function getPropertyValue($data, $propertyPath)
95+
{
96+
try {
97+
return $this->propertyAccessor->getValue($data, $propertyPath);
98+
} catch (AccessException $e) {
99+
if (!$e instanceof UninitializedPropertyException
100+
// For versions without UninitializedPropertyException check the exception message
101+
&& (class_exists(UninitializedPropertyException::class) || false === strpos($e->getMessage(), 'You should initialize it'))
102+
) {
103+
throw $e;
104+
}
105+
106+
return null;
107+
}
108+
}
91109
}

src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php

+35-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
1818
use Symfony\Component\Form\Form;
1919
use Symfony\Component\Form\FormConfigBuilder;
20+
use Symfony\Component\Form\Tests\Fixtures\TypehintedPropertiesCar;
2021
use Symfony\Component\PropertyAccess\PropertyAccess;
2122
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
2223
use Symfony\Component\PropertyAccess\PropertyPath;
@@ -113,6 +114,23 @@ public function testMapDataToFormsIgnoresUnmapped()
113114
$this->assertNull($form->getData());
114115
}
115116

117+
/**
118+
* @requires PHP 7.4
119+
*/
120+
public function testMapDataToFormsIgnoresUninitializedProperties()
121+
{
122+
$engineForm = new Form(new FormConfigBuilder('engine', null, $this->dispatcher));
123+
$colorForm = new Form(new FormConfigBuilder('color', null, $this->dispatcher));
124+
125+
$car = new TypehintedPropertiesCar();
126+
$car->engine = 'BMW';
127+
128+
$this->mapper->mapDataToForms($car, [$engineForm, $colorForm]);
129+
130+
$this->assertSame($car->engine, $engineForm->getData());
131+
$this->assertNull($colorForm->getData());
132+
}
133+
116134
public function testMapDataToFormsSetsDefaultDataIfPassedDataIsNull()
117135
{
118136
$default = new \stdClass();
@@ -293,13 +311,28 @@ public function testMapFormsToDataIgnoresDisabled()
293311
$config->setPropertyPath($propertyPath);
294312
$config->setData($engine);
295313
$config->setDisabled(true);
296-
$form = new Form($config);
314+
$form = new SubmittedForm($config);
297315

298316
$this->mapper->mapFormsToData([$form], $car);
299317

300318
$this->assertSame($initialEngine, $car->engine);
301319
}
302320

321+
/**
322+
* @requires PHP 7.4
323+
*/
324+
public function testMapFormsToUninitializedProperties()
325+
{
326+
$car = new TypehintedPropertiesCar();
327+
$config = new FormConfigBuilder('engine', null, $this->dispatcher);
328+
$config->setData('BMW');
329+
$form = new SubmittedForm($config);
330+
331+
$this->mapper->mapFormsToData([$form], $car);
332+
333+
$this->assertSame('BMW', $car->engine);
334+
}
335+
303336
/**
304337
* @dataProvider provideDate
305338
*/
@@ -339,7 +372,7 @@ public function isSubmitted()
339372
}
340373
}
341374

342-
class NotSynchronizedForm extends Form
375+
class NotSynchronizedForm extends SubmittedForm
343376
{
344377
public function isSynchronized()
345378
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Fixtures;
13+
14+
class TypehintedPropertiesCar
15+
{
16+
public ?string $engine;
17+
public ?string $color;
18+
}

0 commit comments

Comments
 (0)