Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commitca86457

Browse files
committed
feature #42403 [Validator] Define which collection keys should be checked for uniqueness (wkania)
This PR was squashed before being merged into the 6.1 branch.Discussion----------[Validator] Define which collection keys should be checked for uniqueness| Q | A| ------------- | ---| Branch? | 5.4| Bug fix? | no| New feature? | yes| Deprecations? | no| Tickets | Fix #9888| License | MIT| Doc PR |symfony/symfony-docs#16713Currently, the validator checks each element of the collection as a whole. We already have a custom normalizer (which is great), but it would be nice to be able to check for uniqueness certain [collection](https://symfony.com/doc/current/reference/constraints/Collection.html) keys.For example, some fields in the collection element can be identifiers. They should be unique within the collection, even when the rest of the element data are different.Current state:- validates that all the elements of the given collection are uniqueNew state:- preserve the current state,- all old tests pass (no changes in them),- no breaking changes,- define which collection fields should be checked for uniqueness (optional),- fields are optional in each element of the collection. Use [collection constraints](https://symfony.com/doc/current/reference/constraints/Collection.html) if they are requiredExamples:1. Basic example. Each translation of the same resource must be in a different language.```phpuse Symfony\Component\Validator\Constraints as Assert;/** *@Assert\Count(min=1), *@Assert\Unique(fields={"language"}), *@Assert\Collection( * fields = { * "language" = { *@Assert\NotBlank, *@Assert\Length(min = 2, max = 2), *@Assert\Language * }, * "title" = { *@Assert\NotBlank, *@Assert\Length(max = 255) * }, * "description" = { *@Assert\NotBlank, *@Assert\Length(max = 255) * } * } * ) */public array $translations = [];```2. An example where Optional is recognizable. Items with the id are changed and without are new.```phpuse Symfony\Component\Validator\Constraints as Assert;use Symfony\Component\Validator\Constraints\Optional;/** *@Assert\Unique(fields={"id"}), *@Assert\Collection( * fields = { * "id" =@Assert\Optional({ *@Assert\Uuid * }), * "name" = { *@Assert\NotBlank, *@Assert\Length(max = 255) * } * } * ) */public array $items = [];```3. An example with composite uniqueness```phpuse Symfony\Component\Validator\Constraints as Assert;/** *@Assert\Unique(fields={"latitude", "longitude"}), *@Assert\Collection( * fields = { * "latitude" = { *@Assert\NotBlank * }, * "longitude" = { *@Assert\NotBlank * }, * "poi" = { *@Assert\Length(max = 255) * } * } * ) */public array $coordinates = [];```Commits-------0e8f4cefdb [Validator] Define which collection keys should be checked for uniqueness
2 parentsa247942 +ccfbbdb commitca86457

File tree

4 files changed

+100
-7
lines changed

4 files changed

+100
-7
lines changed

‎CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.1
55
---
66

7+
* Add the`fields` option to the`Unique` constraint, to define which collection keys should be checked for uniqueness
78
* Deprecate`Constraint::$errorNames`, use`Constraint::ERROR_NAMES` instead
89
* Deprecate constraint`ExpressionLanguageSyntax`, use`ExpressionSyntax` instead
910
* Add method`__toString()` to`ConstraintViolationInterface` &`ConstraintViolationListInterface`

‎Constraints/Unique.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class Unique extends Constraint
2525
{
2626
publicconstIS_NOT_UNIQUE ='7911c98d-b845-4da0-94b7-a8dac36bc55a';
2727

28+
publicarray|string$fields = [];
29+
2830
protectedconstERROR_NAMES = [
2931
self::IS_NOT_UNIQUE =>'IS_NOT_UNIQUE',
3032
];
@@ -37,17 +39,24 @@ class Unique extends Constraint
3739
public$message ='This collection should contain only unique elements.';
3840
public$normalizer;
3941

42+
/**
43+
* {@inheritdoc}
44+
*
45+
* @param array|string $fields the combination of fields that must contain unique values or a set of options
46+
*/
4047
publicfunction__construct(
4148
array$options =null,
4249
string$message =null,
4350
callable$normalizer =null,
4451
array$groups =null,
45-
mixed$payload =null
52+
mixed$payload =null,
53+
array|string$fields =null,
4654
) {
4755
parent::__construct($options,$groups,$payload);
4856

4957
$this->message =$message ??$this->message;
5058
$this->normalizer =$normalizer ??$this->normalizer;
59+
$this->fields =$fields ??$this->fields;
5160

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

‎Constraints/UniqueValidator.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public function validate(mixed $value, Constraint $constraint)
3030
thrownewUnexpectedTypeException($constraint, Unique::class);
3131
}
3232

33+
$fields = (array)$constraint->fields;
34+
3335
if (null ===$value) {
3436
return;
3537
}
@@ -41,6 +43,10 @@ public function validate(mixed $value, Constraint $constraint)
4143
$collectionElements = [];
4244
$normalizer =$this->getNormalizer($constraint);
4345
foreach ($valueas$element) {
46+
if ($fields && !$element =$this->reduceElementKeys($fields,$element)) {
47+
continue;
48+
}
49+
4450
$element =$normalizer($element);
4551

4652
if (\in_array($element,$collectionElements,true)) {
@@ -65,4 +71,19 @@ private function getNormalizer(Unique $unique): callable
6571

6672
return$unique->normalizer;
6773
}
74+
75+
privatefunctionreduceElementKeys(array$fields,array$element):array
76+
{
77+
$output = [];
78+
foreach ($fieldsas$field) {
79+
if (!\is_string($field)) {
80+
thrownewUnexpectedTypeException($field,'string');
81+
}
82+
if (isset($element[$field])) {
83+
$output[$field] =$element[$field];
84+
}
85+
}
86+
87+
return$output;
88+
}
6889
}

‎Tests/Constraints/UniqueValidatorTest.php

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313

1414
useSymfony\Component\Validator\Constraints\Unique;
1515
useSymfony\Component\Validator\Constraints\UniqueValidator;
16+
useSymfony\Component\Validator\Exception\UnexpectedTypeException;
1617
useSymfony\Component\Validator\Exception\UnexpectedValueException;
1718
useSymfony\Component\Validator\Test\ConstraintValidatorTestCase;
1819

1920
class UniqueValidatorTestextends ConstraintValidatorTestCase
2021
{
21-
protectedfunctioncreateValidator()
22+
protectedfunctioncreateValidator():UniqueValidator
2223
{
2324
returnnewUniqueValidator();
2425
}
@@ -153,15 +154,15 @@ public function testExpectsNonUniqueObjects($callback)
153154
->assertRaised();
154155
}
155156

156-
publicfunctiongetCallback()
157+
publicfunctiongetCallback():array
157158
{
158159
return [
159-
yield'static function' => [staticfunction (\stdClass$object) {
160+
'static function' => [staticfunction (\stdClass$object) {
160161
return [$object->name,$object->email];
161162
}],
162-
yield'callable with string notation' => ['Symfony\Component\Validator\Tests\Constraints\CallableClass::execute'],
163-
yield'callable with static notation' => [[CallableClass::class,'execute']],
164-
yield'callable with object' => [[newCallableClass(),'execute']],
163+
'callable with string notation' => ['Symfony\Component\Validator\Tests\Constraints\CallableClass::execute'],
164+
'callable with static notation' => [[CallableClass::class,'execute']],
165+
'callable with object' => [[newCallableClass(),'execute']],
165166
];
166167
}
167168

@@ -220,6 +221,67 @@ public function testExpectsValidCaseInsensitiveComparison()
220221

221222
$this->assertNoViolation();
222223
}
224+
225+
publicfunctiontestCollectionFieldsAreOptional()
226+
{
227+
$this->validator->validate([['value' =>5], ['id' =>1,'value' =>6]],newUnique(fields:'id'));
228+
229+
$this->assertNoViolation();
230+
}
231+
232+
/**
233+
* @dataProvider getInvalidFieldNames
234+
*/
235+
publicfunctiontestCollectionFieldNamesMustBeString(string$type,mixed$field)
236+
{
237+
$this->expectException(UnexpectedTypeException::class);
238+
$this->expectExceptionMessage(sprintf('Expected argument of type "string", "%s" given',$type));
239+
240+
$this->validator->validate([['value' =>5], ['id' =>1,'value' =>6]],newUnique(fields: [$field]));
241+
}
242+
243+
publicfunctiongetInvalidFieldNames():array
244+
{
245+
return [
246+
['stdClass',new \stdClass()],
247+
['int',2],
248+
['bool',false],
249+
];
250+
}
251+
252+
/**
253+
* @dataProvider getInvalidCollectionValues
254+
*/
255+
publicfunctiontestInvalidCollectionValues(array$value,array$fields)
256+
{
257+
$this->validator->validate($value,newUnique([
258+
'message' =>'myMessage',
259+
], fields:$fields));
260+
261+
$this->buildViolation('myMessage')
262+
->setParameter('{{ value }}','array')
263+
->setCode(Unique::IS_NOT_UNIQUE)
264+
->assertRaised();
265+
}
266+
267+
publicfunctiongetInvalidCollectionValues():array
268+
{
269+
return [
270+
'unique string' => [[
271+
['lang' =>'eng','translation' =>'hi'],
272+
['lang' =>'eng','translation' =>'hello'],
273+
], ['lang']],
274+
'unique floats' => [[
275+
['latitude' =>51.509865,'longitude' => -0.118092,'poi' =>'capital'],
276+
['latitude' =>52.520008,'longitude' =>13.404954],
277+
['latitude' =>51.509865,'longitude' => -0.118092],
278+
], ['latitude','longitude']],
279+
'unique int' => [[
280+
['id' =>1,'email' =>'bar@email.com'],
281+
['id' =>1,'email' =>'foo@email.com'],
282+
], ['id']],
283+
];
284+
}
223285
}
224286

225287
class CallableClass

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp