Skip to content

Commit c8051ce

Browse files
committed
[Validator] Add errorPath to Unique constraint
1 parent 916718c commit c8051ce

File tree

4 files changed

+109
-12
lines changed

4 files changed

+109
-12
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* `IbanValidator` accepts IBANs containing non-breaking and narrow non-breaking spaces
88
* Make `PasswordStrengthValidator::estimateStrength()` public
99
* Add the `Yaml` constraint for validating YAML content
10+
* Add `errorPath` to Unique constraint
1011

1112
7.1
1213
---

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Unique extends Constraint
2525
public const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a';
2626

2727
public array|string $fields = [];
28+
public ?string $errorPath = null;
2829

2930
protected const ERROR_NAMES = [
3031
self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE',
@@ -46,12 +47,14 @@ public function __construct(
4647
?array $groups = null,
4748
mixed $payload = null,
4849
array|string|null $fields = null,
50+
?string $errorPath = null,
4951
) {
5052
parent::__construct($options, $groups, $payload);
5153

5254
$this->message = $message ?? $this->message;
5355
$this->normalizer = $normalizer ?? $this->normalizer;
5456
$this->fields = $fields ?? $this->fields;
57+
$this->errorPath = $errorPath ?? $this->errorPath;
5558

5659
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
5760
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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function validate(mixed $value, Constraint $constraint): void
3939

4040
$collectionElements = [];
4141
$normalizer = $this->getNormalizer($constraint);
42-
foreach ($value as $element) {
42+
foreach ($value as $index => $element) {
4343
$element = $normalizer($element);
4444

4545
if ($fields && !$element = $this->reduceElementKeys($fields, $element)) {
@@ -48,6 +48,7 @@ public function validate(mixed $value, Constraint $constraint): void
4848

4949
if (\in_array($element, $collectionElements, true)) {
5050
$this->context->buildViolation($constraint->message)
51+
->atPath("[$index]".(null !== $constraint->errorPath ? ".{$constraint->errorPath}" : ''))
5152
->setParameter('{{ value }}', $this->formatValue($value))
5253
->setCode(Unique::IS_NOT_UNIQUE)
5354
->addViolation();

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

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static function getValidValues()
6161
/**
6262
* @dataProvider getInvalidValues
6363
*/
64-
public function testInvalidValues($value)
64+
public function testInvalidValues($value, string $expectedErrorPath)
6565
{
6666
$constraint = new Unique([
6767
'message' => 'myMessage',
@@ -71,6 +71,7 @@ public function testInvalidValues($value)
7171
$this->buildViolation('myMessage')
7272
->setParameter('{{ value }}', 'array')
7373
->setCode(Unique::IS_NOT_UNIQUE)
74+
->atPath($expectedErrorPath)
7475
->assertRaised();
7576
}
7677

@@ -79,12 +80,12 @@ public static function getInvalidValues()
7980
$object = new \stdClass();
8081

8182
return [
82-
yield 'not unique booleans' => [[true, true]],
83-
yield 'not unique integers' => [[1, 2, 3, 3]],
84-
yield 'not unique floats' => [[0.1, 0.2, 0.1]],
85-
yield 'not unique string' => [['a', 'b', 'a']],
86-
yield 'not unique arrays' => [[[1, 1], [2, 3], [1, 1]]],
87-
yield 'not unique objects' => [[$object, $object]],
83+
yield 'not unique booleans' => [[true, true], 'property.path[1]'],
84+
yield 'not unique integers' => [[1, 2, 3, 3], 'property.path[3]'],
85+
yield 'not unique floats' => [[0.1, 0.2, 0.1], 'property.path[2]'],
86+
yield 'not unique string' => [['a', 'b', 'a'], 'property.path[2]'],
87+
yield 'not unique arrays' => [[[1, 1], [2, 3], [1, 1]], 'property.path[2]'],
88+
yield 'not unique objects' => [[$object, $object], 'property.path[1]'],
8889
];
8990
}
9091

@@ -96,6 +97,7 @@ public function testInvalidValueNamed()
9697
$this->buildViolation('myMessage')
9798
->setParameter('{{ value }}', 'array')
9899
->setCode(Unique::IS_NOT_UNIQUE)
100+
->atPath('property.path[3]')
99101
->assertRaised();
100102
}
101103

@@ -152,6 +154,7 @@ public function testExpectsNonUniqueObjects($callback)
152154
$this->buildViolation('myMessage')
153155
->setParameter('{{ value }}', 'array')
154156
->setCode(Unique::IS_NOT_UNIQUE)
157+
->atPath('property.path[2]')
155158
->assertRaised();
156159
}
157160

@@ -176,6 +179,7 @@ public function testExpectsInvalidNonStrictComparison()
176179
$this->buildViolation('myMessage')
177180
->setParameter('{{ value }}', 'array')
178181
->setCode(Unique::IS_NOT_UNIQUE)
182+
->atPath('property.path[1]')
179183
->assertRaised();
180184
}
181185

@@ -202,6 +206,7 @@ public function testExpectsInvalidCaseInsensitiveComparison()
202206
$this->buildViolation('myMessage')
203207
->setParameter('{{ value }}', 'array')
204208
->setCode(Unique::IS_NOT_UNIQUE)
209+
->atPath('property.path[1]')
205210
->assertRaised();
206211
}
207212

@@ -246,7 +251,7 @@ public static function getInvalidFieldNames(): array
246251
/**
247252
* @dataProvider getInvalidCollectionValues
248253
*/
249-
public function testInvalidCollectionValues(array $value, array $fields)
254+
public function testInvalidCollectionValues(array $value, array $fields, string $expectedErrorPath)
250255
{
251256
$this->validator->validate($value, new Unique([
252257
'message' => 'myMessage',
@@ -255,6 +260,7 @@ public function testInvalidCollectionValues(array $value, array $fields)
255260
$this->buildViolation('myMessage')
256261
->setParameter('{{ value }}', 'array')
257262
->setCode(Unique::IS_NOT_UNIQUE)
263+
->atPath($expectedErrorPath)
258264
->assertRaised();
259265
}
260266

@@ -264,23 +270,25 @@ public static function getInvalidCollectionValues(): array
264270
'unique string' => [[
265271
['lang' => 'eng', 'translation' => 'hi'],
266272
['lang' => 'eng', 'translation' => 'hello'],
267-
], ['lang']],
273+
], ['lang'], 'property.path[1]'],
268274
'unique floats' => [[
269275
['latitude' => 51.509865, 'longitude' => -0.118092, 'poi' => 'capital'],
270276
['latitude' => 52.520008, 'longitude' => 13.404954],
271277
['latitude' => 51.509865, 'longitude' => -0.118092],
272-
], ['latitude', 'longitude']],
278+
], ['latitude', 'longitude'], 'property.path[2]'],
273279
'unique int' => [[
274280
['id' => 1, 'email' => 'bar@email.com'],
275281
['id' => 1, 'email' => 'foo@email.com'],
276-
], ['id']],
282+
], ['id'], 'property.path[1]'],
277283
'unique null' => [
278284
[null, null],
279285
[],
286+
'property.path[1]',
280287
],
281288
'unique field null' => [
282289
[['nullField' => null], ['nullField' => null]],
283290
['nullField'],
291+
'property.path[1]',
284292
],
285293
];
286294
}
@@ -308,6 +316,90 @@ public function testArrayOfObjectsUnique()
308316
$this->assertNoViolation();
309317
}
310318

319+
public function testErrorPath()
320+
{
321+
$array = [
322+
new DummyClassOne(),
323+
new DummyClassOne(),
324+
new DummyClassOne(),
325+
];
326+
327+
$array[0]->code = 'a1';
328+
$array[1]->code = 'a2';
329+
$array[2]->code = 'a1';
330+
331+
$this->validator->validate(
332+
$array,
333+
new Unique(
334+
normalizer: [self::class, 'normalizeDummyClassOne'],
335+
fields: 'code',
336+
errorPath: 'code',
337+
)
338+
);
339+
340+
$this->buildViolation('This collection should contain only unique elements.')
341+
->setParameter('{{ value }}', 'array')
342+
->setCode(Unique::IS_NOT_UNIQUE)
343+
->atPath('property.path[2].code')
344+
->assertRaised();
345+
}
346+
347+
public function testErrorPathWithIteratorAggregate()
348+
{
349+
$array = new \ArrayObject([
350+
new DummyClassOne(),
351+
new DummyClassOne(),
352+
new DummyClassOne(),
353+
]);
354+
355+
$array[0]->code = 'a1';
356+
$array[1]->code = 'a2';
357+
$array[2]->code = 'a1';
358+
359+
$this->validator->validate(
360+
$array,
361+
new Unique(
362+
normalizer: [self::class, 'normalizeDummyClassOne'],
363+
fields: 'code',
364+
errorPath: 'code',
365+
)
366+
);
367+
368+
$this->buildViolation('This collection should contain only unique elements.')
369+
->setParameter('{{ value }}', 'object')
370+
->setCode(Unique::IS_NOT_UNIQUE)
371+
->atPath('property.path[2].code')
372+
->assertRaised();
373+
}
374+
375+
public function testErrorPathWithNonList()
376+
{
377+
$array = [
378+
'a' => new DummyClassOne(),
379+
'b' => new DummyClassOne(),
380+
'c' => new DummyClassOne(),
381+
];
382+
383+
$array['a']->code = 'a1';
384+
$array['b']->code = 'a2';
385+
$array['c']->code = 'a1';
386+
387+
$this->validator->validate(
388+
$array,
389+
new Unique(
390+
normalizer: [self::class, 'normalizeDummyClassOne'],
391+
fields: 'code',
392+
errorPath: 'code',
393+
)
394+
);
395+
396+
$this->buildViolation('This collection should contain only unique elements.')
397+
->setParameter('{{ value }}', 'array')
398+
->setCode(Unique::IS_NOT_UNIQUE)
399+
->atPath('property.path[c].code')
400+
->assertRaised();
401+
}
402+
311403
public static function normalizeDummyClassOne(DummyClassOne $obj): array
312404
{
313405
return [

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy