diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 3176ac22f9678..15193d5640a87 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -251,8 +251,9 @@ public function testValidateUniquenessWithNull(UniqueEntity $constraint) /** * @dataProvider provideConstraintsWithIgnoreNullDisabled + * @dataProvider provideConstraintsWithIgnoreNullEnabledOnFirstField */ - public function testValidateUniquenessWithIgnoreNullDisabled(UniqueEntity $constraint) + public function testValidateUniquenessWithIgnoreNullDisableOnSecondField(UniqueEntity $constraint) { $entity1 = new DoubleNameEntity(1, 'Foo', null); $entity2 = new DoubleNameEntity(2, 'Foo', null); @@ -304,6 +305,7 @@ public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgno /** * @dataProvider provideConstraintsWithIgnoreNullEnabled + * @dataProvider provideConstraintsWithIgnoreNullEnabledOnFirstField */ public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored(UniqueEntity $constraint) { @@ -338,6 +340,18 @@ public static function provideConstraintsWithIgnoreNullEnabled(): iterable yield 'Named arguments' => [new UniqueEntity(message: 'myMessage', fields: ['name', 'name2'], em: 'foo', ignoreNull: true)]; } + public static function provideConstraintsWithIgnoreNullEnabledOnFirstField(): iterable + { + yield 'Doctrine style (name field)' => [new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name', 'name2'], + 'em' => self::EM_NAME, + 'ignoreNull' => 'name', + ])]; + + yield 'Named arguments (name field)' => [new UniqueEntity(message: 'myMessage', fields: ['name', 'name2'], em: 'foo', ignoreNull: 'name')]; + } + public function testValidateUniquenessWithValidCustomErrorPath() { $constraint = new UniqueEntity([ diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index da1873cb91856..2dd5c7125aa2d 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -45,7 +45,8 @@ class UniqueEntity extends Constraint protected static $errorNames = self::ERROR_NAMES; /** - * @param array|string $fields the combination of fields that must contain unique values or a set of options + * @param array|string $fields The combination of fields that must contain unique values or a set of options + * @param bool|array|string $ignoreNull The combination of fields that ignore null values */ public function __construct( $fields, @@ -55,7 +56,7 @@ public function __construct( string $entityClass = null, string $repositoryMethod = null, string $errorPath = null, - bool $ignoreNull = null, + bool|string|array $ignoreNull = null, array $groups = null, $payload = null, array $options = [] diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 67575134b660b..a69bcad8ef323 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -87,7 +87,7 @@ public function validate(mixed $entity, Constraint $constraint) $class = $em->getClassMetadata($entity::class); $criteria = []; - $hasNullValue = false; + $hasIgnorableNullValue = false; foreach ($fields as $fieldName) { if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) { @@ -96,11 +96,9 @@ public function validate(mixed $entity, Constraint $constraint) $fieldValue = $class->reflFields[$fieldName]->getValue($entity); - if (null === $fieldValue) { - $hasNullValue = true; - } + if (null === $fieldValue && $this->ignoreNullForField($constraint, $fieldName)) { + $hasIgnorableNullValue = true; - if ($constraint->ignoreNull && null === $fieldValue) { continue; } @@ -116,7 +114,7 @@ public function validate(mixed $entity, Constraint $constraint) } // validation doesn't fail if one of the fields is null and if null values should be ignored - if ($hasNullValue && $constraint->ignoreNull) { + if ($hasIgnorableNullValue) { return; } @@ -195,6 +193,15 @@ public function validate(mixed $entity, Constraint $constraint) ->addViolation(); } + private function ignoreNullForField(UniqueEntity $constraint, string $fieldName): bool + { + if (\is_bool($constraint->ignoreNull)) { + return $constraint->ignoreNull; + } + + return \in_array($fieldName, (array) $constraint->ignoreNull, true); + } + private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, mixed $value): string { if (!\is_object($value) || $value instanceof \DateTimeInterface) {