Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fd9c5b4

Browse filesBrowse files
feature #59274 [Validator] Allow Unique constraint validation on all elements (Jean-Beru)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [Validator] Allow Unique constraint validation on all elements | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT Sometimes it could be useful to assert that every elements of a collection is not duplicated. This PR adds a `multipleErrors` option to the Unique constraint to avoid stopping at the first violation. Its value is `false` by default to avoid BC breaks: ```php $violations = $this->validator->validate( ['a1', 'a2', 'a1', 'a1', 'a2'], new Unique(), ); // 1 violation on [2] ``` Now ```php $violations = $this->validator->validate( ['a1', 'a2', 'a1', 'a1', 'a2'], new Unique(multipleErrors: true), ); // 3 violations on [2], [3] and [4] ``` Commits ------- 3fc871e [Validator] Allow Unique constraint validation on all elements
2 parents 8d8183f + 3fc871e commit fd9c5b4
Copy full SHA for fd9c5b4

File tree

4 files changed

+88
-57
lines changed
Filter options

4 files changed

+88
-57
lines changed

‎src/Symfony/Component/Validator/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/CHANGELOG.md
+1-48Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,12 @@ CHANGELOG
55
---
66

77
* Deprecate defining custom constraints not supporting named arguments
8-
9-
Before:
10-
11-
```php
12-
use Symfony\Component\Validator\Constraint;
13-
14-
class CustomConstraint extends Constraint
15-
{
16-
public function __construct(array $options)
17-
{
18-
// ...
19-
}
20-
}
21-
```
22-
23-
After:
24-
25-
```php
26-
use Symfony\Component\Validator\Attribute\HasNamedArguments;
27-
use Symfony\Component\Validator\Constraint;
28-
29-
class CustomConstraint extends Constraint
30-
{
31-
#[HasNamedArguments]
32-
public function __construct($option1, $option2, $groups, $payload)
33-
{
34-
// ...
35-
}
36-
}
37-
```
388
* Deprecate passing an array of options to the constructors of the constraint classes, pass each option as a dedicated argument instead
39-
40-
Before:
41-
42-
```php
43-
new NotNull([
44-
'groups' => ['foo', 'bar'],
45-
'message' => 'a custom constraint violation message',
46-
])
47-
```
48-
49-
After:
50-
51-
```php
52-
new NotNull(
53-
groups: ['foo', 'bar'],
54-
message: 'a custom constraint violation message',
55-
)
56-
```
579
* Add support for ratio checks for SVG files to the `Image` constraint
5810
* Add the `Slug` constraint
5911
* Add support for the `otherwise` option in the `When` constraint
6012
* Add support for multiple fields containing nested constraints in `Composite` constraints
13+
* Add the `stopOnFirstError` option to the `Unique` constraint to validate all elements
6114

6215
7.2
6316
---

‎src/Symfony/Component/Validator/Constraints/Unique.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/Unique.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Unique extends Constraint
2727

2828
public array|string $fields = [];
2929
public ?string $errorPath = null;
30+
public bool $stopOnFirstError = true;
3031

3132
protected const ERROR_NAMES = [
3233
self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE',
@@ -50,6 +51,7 @@ public function __construct(
5051
mixed $payload = null,
5152
array|string|null $fields = null,
5253
?string $errorPath = null,
54+
?bool $stopOnFirstError = null,
5355
) {
5456
if (\is_array($options)) {
5557
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
@@ -61,6 +63,7 @@ public function __construct(
6163
$this->normalizer = $normalizer ?? $this->normalizer;
6264
$this->fields = $fields ?? $this->fields;
6365
$this->errorPath = $errorPath ?? $this->errorPath;
66+
$this->stopOnFirstError = $stopOnFirstError ?? $this->stopOnFirstError;
6467

6568
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
6669
throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));

‎src/Symfony/Component/Validator/Constraints/UniqueValidator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/UniqueValidator.php
+13-9Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,24 @@ public function validate(mixed $value, Constraint $constraint): void
4646
continue;
4747
}
4848

49-
if (\in_array($element, $collectionElements, true)) {
50-
$violationBuilder = $this->context->buildViolation($constraint->message)
51-
->setParameter('{{ value }}', $this->formatValue($element))
52-
->setCode(Unique::IS_NOT_UNIQUE);
49+
if (!\in_array($element, $collectionElements, true)) {
50+
$collectionElements[] = $element;
51+
continue;
52+
}
5353

54-
if (null !== $constraint->errorPath) {
55-
$violationBuilder->atPath("[$index].{$constraint->errorPath}");
56-
}
54+
$violationBuilder = $this->context->buildViolation($constraint->message)
55+
->setParameter('{{ value }}', $this->formatValue($element))
56+
->setCode(Unique::IS_NOT_UNIQUE);
57+
58+
if (!$constraint->stopOnFirstError || null !== $constraint->errorPath) {
59+
$violationBuilder->atPath("[$index]".(null !== $constraint->errorPath ? ".{$constraint->errorPath}" : ''));
60+
}
5761

58-
$violationBuilder->addViolation();
62+
$violationBuilder->addViolation();
5963

64+
if ($constraint->stopOnFirstError) {
6065
return;
6166
}
62-
$collectionElements[] = $element;
6367
}
6468
}
6569

‎src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php
+71Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,77 @@ public function testErrorPathWithNonList()
387387
->assertRaised();
388388
}
389389

390+
public function testWithoutStopOnFirstError()
391+
{
392+
$this->validator->validate(
393+
['a1', 'a2', 'a1', 'a1', 'a2'],
394+
new Unique(stopOnFirstError: false),
395+
);
396+
397+
$this
398+
->buildViolation('This collection should contain only unique elements.')
399+
->setParameter('{{ value }}', '"a1"')
400+
->setCode(Unique::IS_NOT_UNIQUE)
401+
->atPath('property.path[2]')
402+
403+
->buildNextViolation('This collection should contain only unique elements.')
404+
->setParameter('{{ value }}', '"a1"')
405+
->setCode(Unique::IS_NOT_UNIQUE)
406+
->atPath('property.path[3]')
407+
408+
->buildNextViolation('This collection should contain only unique elements.')
409+
->setParameter('{{ value }}', '"a2"')
410+
->setCode(Unique::IS_NOT_UNIQUE)
411+
->atPath('property.path[4]')
412+
413+
->assertRaised();
414+
}
415+
416+
public function testWithoutStopOnFirstErrorWithErrorPath()
417+
{
418+
$array = [
419+
new DummyClassOne(),
420+
new DummyClassOne(),
421+
new DummyClassOne(),
422+
new DummyClassOne(),
423+
new DummyClassOne(),
424+
];
425+
426+
$array[0]->code = 'a1';
427+
$array[1]->code = 'a2';
428+
$array[2]->code = 'a1';
429+
$array[3]->code = 'a1';
430+
$array[4]->code = 'a2';
431+
432+
$this->validator->validate(
433+
$array,
434+
new Unique(
435+
normalizer: [self::class, 'normalizeDummyClassOne'],
436+
fields: 'code',
437+
errorPath: 'code',
438+
stopOnFirstError: false,
439+
)
440+
);
441+
442+
$this
443+
->buildViolation('This collection should contain only unique elements.')
444+
->setParameter('{{ value }}', 'array')
445+
->setCode(Unique::IS_NOT_UNIQUE)
446+
->atPath('property.path[2].code')
447+
448+
->buildNextViolation('This collection should contain only unique elements.')
449+
->setParameter('{{ value }}', 'array')
450+
->setCode(Unique::IS_NOT_UNIQUE)
451+
->atPath('property.path[3].code')
452+
453+
->buildNextViolation('This collection should contain only unique elements.')
454+
->setParameter('{{ value }}', 'array')
455+
->setCode(Unique::IS_NOT_UNIQUE)
456+
->atPath('property.path[4].code')
457+
458+
->assertRaised();
459+
}
460+
390461
public static function normalizeDummyClassOne(DummyClassOne $obj): array
391462
{
392463
return [

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.