Skip to content

Commit

Permalink
feature #49293 [DoctrineBridge] Allow to ignore specific nullable fie…
Browse files Browse the repository at this point in the history
…lds in UniqueEntity (VincentLanglet)

This PR was squashed before being merged into the 6.3 branch.

Discussion
----------

[DoctrineBridge] Allow to ignore specific nullable fields in UniqueEntity

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead -->
| License       | MIT
| Doc PR        | symfony/symfony-docs#...  TODO

When the UniqueEntity is applied on multiple things the only option was
- ignore all nullable values
- do not ignore any nullable values

With this feature, it would be possible to ignore nullable values for specific field only and do not ignore nullable values for others.

Commits
-------

0e61a66 [DoctrineBridge] Allow to ignore specific nullable fields in UniqueEntity
  • Loading branch information
nicolas-grekas committed May 19, 2023
2 parents eab290d + 0e61a66 commit 95aa09e
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -304,6 +305,7 @@ public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgno

/**
* @dataProvider provideConstraintsWithIgnoreNullEnabled
* @dataProvider provideConstraintsWithIgnoreNullEnabledOnFirstField
*/
public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored(UniqueEntity $constraint)
{
Expand Down Expand Up @@ -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([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 95aa09e

Please sign in to comment.