From 1b35b845c6d126fbabdbe6aef90c9ec8460f76a5 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 24 Jul 2024 08:39:19 +0200 Subject: [PATCH] treat uninitialized properties referenced by property paths as null --- .../AbstractComparisonValidator.php | 3 ++ .../Validator/Constraints/BicValidator.php | 3 ++ .../Validator/Constraints/RangeValidator.php | 3 ++ .../AbstractComparisonValidatorTestCase.php | 28 ++++++++++++++++++ .../Tests/Constraints/BicValidatorTest.php | 13 +++++++++ .../Constraints/Fixtures/BicTypedDummy.php | 17 +++++++++++ .../Constraints/Fixtures/MinMaxTyped.php | 18 ++++++++++++ .../Tests/Constraints/Fixtures/TypedDummy.php | 17 +++++++++++ ...idatorWithNegativeOrZeroConstraintTest.php | 10 +++++++ ...hanValidatorWithNegativeConstraintTest.php | 10 +++++++ .../Tests/Constraints/RangeValidatorTest.php | 29 +++++++++++++++++++ src/Symfony/Component/Validator/composer.json | 2 +- 12 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/Fixtures/BicTypedDummy.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/Fixtures/MinMaxTyped.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/Fixtures/TypedDummy.php diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php index b179fe81095dd..90e022671e15e 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Validator\Constraint; @@ -56,6 +57,8 @@ public function validate($value, Constraint $constraint) $comparedValue = $this->getPropertyAccessor()->getValue($object, $path); } catch (NoSuchPropertyException $e) { throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e); + } catch (UninitializedPropertyException $e) { + $comparedValue = null; } } else { $comparedValue = $constraint->value; diff --git a/src/Symfony/Component/Validator/Constraints/BicValidator.php b/src/Symfony/Component/Validator/Constraints/BicValidator.php index 240f2dd26c41a..fa458b196cef3 100644 --- a/src/Symfony/Component/Validator/Constraints/BicValidator.php +++ b/src/Symfony/Component/Validator/Constraints/BicValidator.php @@ -13,6 +13,7 @@ use Symfony\Component\Intl\Countries; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Validator\Constraint; @@ -130,6 +131,8 @@ public function validate($value, Constraint $constraint) $iban = $this->getPropertyAccessor()->getValue($object, $path); } catch (NoSuchPropertyException $e) { throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e); + } catch (UninitializedPropertyException $e) { + $iban = null; } } if (!$iban) { diff --git a/src/Symfony/Component/Validator/Constraints/RangeValidator.php b/src/Symfony/Component/Validator/Constraints/RangeValidator.php index 3268e0da21f30..9a0d7177cd8e4 100644 --- a/src/Symfony/Component/Validator/Constraints/RangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RangeValidator.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Validator\Constraint; @@ -178,6 +179,8 @@ private function getLimit(?string $propertyPath, $default, Constraint $constrain return $this->getPropertyAccessor()->getValue($object, $propertyPath); } catch (NoSuchPropertyException $e) { throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $propertyPath, get_debug_type($constraint)).$e->getMessage(), 0, $e); + } catch (UninitializedPropertyException $e) { + return null; } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php index 9d3c99c983473..6501b7edb1891 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php @@ -16,6 +16,7 @@ use Symfony\Component\Validator\Constraints\AbstractComparison; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; +use Symfony\Component\Validator\Tests\Constraints\Fixtures\TypedDummy; class ComparisonTest_Class { @@ -274,6 +275,33 @@ public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsS } } + /** + * @requires PHP 7.4 + * + * @dataProvider provideComparisonsToNullValueAtPropertyPath + */ + public function testCompareWithUninitializedPropertyAtPropertyPath($dirtyValue, $dirtyValueAsString, $isValid) + { + $this->setObject(new TypedDummy()); + + $this->validator->validate($dirtyValue, $this->createConstraint([ + 'message' => 'Constraint Message', + 'propertyPath' => 'value', + ])); + + if ($isValid) { + $this->assertNoViolation(); + } else { + $this->buildViolation('Constraint Message') + ->setParameter('{{ value }}', $dirtyValueAsString) + ->setParameter('{{ compared_value }}', 'null') + ->setParameter('{{ compared_value_type }}', 'null') + ->setParameter('{{ compared_value_path }}', 'value') + ->setCode($this->getErrorCode()) + ->assertRaised(); + } + } + public static function provideAllInvalidComparisons(): array { // The provider runs before setUp(), so we need to manually fix diff --git a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php index 536e74b073a74..1eac0fa47d374 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; +use Symfony\Component\Validator\Tests\Constraints\Fixtures\BicTypedDummy; class BicValidatorTest extends ConstraintValidatorTestCase { @@ -92,6 +93,18 @@ public function testInvalidComparisonToPropertyPathFromAttribute() ->assertRaised(); } + /** + * @requires PHP 7.4 + */ + public function testPropertyPathReferencingUninitializedProperty() + { + $this->setObject(new BicTypedDummy()); + + $this->validator->validate('UNCRIT2B912', new Bic(['ibanPropertyPath' => 'iban'])); + + $this->assertNoViolation(); + } + public function testValidComparisonToValue() { $constraint = new Bic(['iban' => 'FR14 2004 1010 0505 0001 3M02 606']); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/BicTypedDummy.php b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/BicTypedDummy.php new file mode 100644 index 0000000000000..90ad4009c15c1 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/BicTypedDummy.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints\Fixtures; + +class BicTypedDummy +{ + public string $iban; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/MinMaxTyped.php b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/MinMaxTyped.php new file mode 100644 index 0000000000000..1595030fa6a63 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/MinMaxTyped.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\Validator\Tests\Constraints\Fixtures; + +class MinMaxTyped +{ + public int $min; + public int $max; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/TypedDummy.php b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/TypedDummy.php new file mode 100644 index 0000000000000..73d2543376c43 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/TypedDummy.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints\Fixtures; + +class TypedDummy +{ + public mixed $value; +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php index 0702d38406228..7d4c7fb139e0d 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php @@ -121,6 +121,16 @@ public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsS $this->markTestSkipped('PropertyPath option is not used in NegativeOrZero constraint'); } + /** + * @requires PHP 7.4 + * + * @dataProvider provideComparisonsToNullValueAtPropertyPath + */ + public function testCompareWithUninitializedPropertyAtPropertyPath($dirtyValue, $dirtyValueAsString, $isValid) + { + $this->markTestSkipped('PropertyPath option is not used in NegativeOrZero constraint'); + } + public static function throwsOnInvalidStringDatesProvider(): array { self::markTestSkipped('The "value" option cannot be used in the NegativeOrZero constraint'); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php index 7f969f97d5892..ccf52e3065ff2 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php @@ -121,6 +121,16 @@ public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsS $this->markTestSkipped('PropertyPath option is not used in Negative constraint'); } + /** + * @requires PHP 7.4 + * + * @dataProvider provideComparisonsToNullValueAtPropertyPath + */ + public function testCompareWithUninitializedPropertyAtPropertyPath($dirtyValue, $dirtyValueAsString, $isValid) + { + $this->markTestSkipped('PropertyPath option is not used in Negative constraint'); + } + public function testInvalidComparisonToPropertyPathAddsPathAsParameter() { $this->markTestSkipped('PropertyPath option is not used in Negative constraint'); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php index 83a2a3d596048..01e606d63852e 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Validator\Constraints\RangeValidator; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; +use Symfony\Component\Validator\Tests\Constraints\Fixtures\MinMaxTyped; use Symfony\Component\Validator\Tests\IcuCompatibilityTrait; class RangeValidatorTest extends ConstraintValidatorTestCase @@ -1042,6 +1043,34 @@ public function testInvalidDatesCombinedMinPropertyPath($value, $dateTimeAsStrin ->assertRaised(); } + /** + * @requires PHP 7.4 + */ + public function testMinPropertyPathReferencingUninitializedProperty() + { + $object = new MinMaxTyped(); + $object->max = 5; + $this->setObject($object); + + $this->validator->validate(5, new Range(['minPropertyPath' => 'min', 'maxPropertyPath' => 'max'])); + + $this->assertNoViolation(); + } + + /** + * @requires PHP 7.4 + */ + public function testMaxPropertyPathReferencingUninitializedProperty() + { + $object = new MinMaxTyped(); + $object->min = 5; + $this->setObject($object); + + $this->validator->validate(5, new Range(['minPropertyPath' => 'min', 'maxPropertyPath' => 'max'])); + + $this->assertNoViolation(); + } + public static function provideMessageIfMinAndMaxSet(): array { $notInRangeMessage = (new Range(['min' => '']))->notInRangeMessage; diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 19a27b3333a81..9d1fc800fd26e 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -38,7 +38,7 @@ "symfony/expression-language": "^5.1|^6.0", "symfony/cache": "^4.4|^5.0|^6.0", "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.0|^6.0", + "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.3|^6.0", "symfony/translation": "^5.4.35|~6.3.12|^6.4.3", "doctrine/annotations": "^1.13|^2",