8000 feature #38653 [DoctrineBridge] Enabled to use the UniqueEntity const… · symfony/symfony@35dad22 · GitHub
[go: up one dir, main page]

Skip to content

Commit 35dad22

Browse files
committed
feature #38653 [DoctrineBridge] Enabled to use the UniqueEntity constraint as an attribute (derrabus)
This PR was merged into the 5.x branch. Discussion ---------- [DoctrineBridge] Enabled to use the UniqueEntity constraint as an attribute | Q | A | ------------- | --- | Branch? | 5.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #38096 | License | MIT | Doc PR | TODO with symfony/symfony-docs#14305 Now that we have a compatible Doctrine version, I could patch and test the missing `UniqueEntity` constraint. Commits ------- 5e7d3ab Enabled to use the UniqueEntity constraint as an attribute.
2 parents 7b62f09 + 5e7d3ab commit 35dad22

File tree

4 files changed

+212
-53
lines changed

4 files changed

+212
-53
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
16+
use Symfony\Component\Validator\Mapping\ClassMetadata;
17+
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
18+
19+
/**
20+
* @requires PHP 8
21+
*/
22+
class UniqueEntityTest extends TestCase
23+
{
24+
public function testAttributeWithDefaultProperty()
25+
{
26+
$metadata = new ClassMetadata(UniqueEntityDummyOne::class);
27+
$loader = new AnnotationLoader();
28+
self::assertTrue($loader->loadClassMetadata($metadata));
29+
30+
/** @var UniqueEntity $constraint */
31+
list($constraint) = $metadata->getConstraints();
32+
self::assertSame(['email'], $constraint->fields);
33+
self::assertTrue($constraint->ignoreNull);
34+
self::assertSame('doctrine.orm.validator.unique', $constraint->validatedBy());
35+
self::assertSame(['Default', 'UniqueEntityDummyOne'], $constraint->groups);
36+
}
37+
38+
public function testAttributeWithCustomizedService()
39+
{
40+
$metadata = new ClassMetadata(UniqueEntityDummyTwo::class);
41+
$loader = new AnnotationLoader();
42+
self::assertTrue($loader->loadClassMetadata($metadata));
43+
44+
/** @var UniqueEntity $constraint */
45+
list($constraint) = $metadata->getConstraints();
46+
self::assertSame(['isbn'], $constraint->fields);
47+
self::assertSame('my_own_validator', $constraint->validatedBy());
48+
self::assertSame('my_own_entity_manager', $constraint->em);
49+
self::assertSame('App\Entity\MyEntity', $constraint->entityClass);
50+
self::assertSame('fetchDifferently', $constraint->repositoryMethod);
51+
}
52+
53+
public function testAttributeWithGroupsAndPaylod()
54+
{
55+
$metadata = new ClassMetadata(UniqueEntityDummyThree::class);
56+
$loader = new AnnotationLoader();
57+
self::assertTrue($loader->loadClassMetadata($metadata));
58+
59+
/** @var UniqueEntity $constraint */
60+
list($constraint) = $metadata->getConstraints();
61+
self::assertSame('uuid', $constraint->fields);
62+
self::assertSame('id', $constraint->errorPath);
63+
self::assertSame('some attached data', $constraint->payload);
64+
self::assertSame(['some_group'], $constraint->groups);
65+
}
66+
}
67+
68+
#[UniqueEntity(['email'], message: 'myMessage')]
69+
class UniqueEntityDummyOne
70+
{
71+
private $email;
72+
}
73+
74+
#[UniqueEntity(fields: ['isbn'], service: 'my_own_validator', em: 'my_own_entity_manager', entityClass: 'App\Entity\MyEntity', repositoryMethod: 'fetchDifferently')]
75+
class UniqueEntityDummyTwo
76+
{
77+
private $isbn;
78+
}
79+
80+
#[UniqueEntity('uuid', ignoreNull: false, errorPath: 'id', payload: 'some attached data', groups: ['some_group'])]
81+
class UniqueEntityDummyThree
82+
{
83+
private $id;
84+
private $uuid;
85+
}

src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php

Lines changed: 89 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,11 @@ private function createSchema($em)
162162

163163
/**
164164
* This is a functional test as there is a large integration necessary to get the validator working.
165+
*
166+
* @dataProvider provideUniquenessConstraints
165167
*/
166-
public function testValidateUniqueness()
168+
public function testValidateUniqueness(UniqueEntity $constraint)
167169
{
168-
$constraint = new UniqueEntity([
169-
'message' => 'myMessage',
170-
'fields' => ['name'],
171-
'em' => self::EM_NAME,
172-
]);
173-
174170
$entity1 = new SingleIntIdEntity(1, 'Foo');
175171
$entity2 = new SingleIntIdEntity(2, 'Foo');
176172

@@ -196,15 +192,24 @@ public function testValidateUniqueness()
196192
->assertRaised();
197193
}
198194

199-
public function testValidateCustomErrorPath()
195+
public function provideUniquenessConstraints(): iterable
200196
{
201-
$constraint = new UniqueEntity([
197+
yield 'Doctrine style' => [new UniqueEntity([
202198
'message' => 'myMessage',
203199
'fields' => ['name'],
204200
'em' => self::EM_NAME,
205-
'errorPath' => 'bar',
206-
]);
201+
])];
202+
203+
if (\PHP_VERSION_ID >= 80000) {
204+
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name"], em: "foo");')];
205+
}
206+
}
207207

208+
/**
209+
* @dataProvider provideConstraintsWithCustomErrorPath
210+
*/
211+
public function testValidateCustomErrorPath(UniqueEntity $constraint)
212+
{
208213
$entity1 = new SingleIntIdEntity(1, 'Foo');
209214
$entity2 = new SingleIntIdEntity(2, 'Foo');
210215

@@ -222,14 +227,25 @@ public function testValidateCustomErrorPath()
222227
->assertRaised();
223228
}
224229

225-
public function testValidateUniquenessWithNull()
230+
public function provideConstraintsWithCustomErrorPath(): iterable
226231
{
227-
$constraint = new UniqueEntity([
232+
yield 'Doctrine style' => [new UniqueEntity([
228233
'message' => 'myMessage',
229234
'fields' => ['name'],
230235
'em' => self::EM_NAME,
231-
]);
236+
'errorPath' => 'bar',
237+
])];
232238

239+
if (\PHP_VERSION_ID >= 80000) {
240+
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name"], em: "foo", errorPath: "bar");')];
241+
}
242+
}
243+
244+
/**
245+
* @dataProvider provideUniquenessConstraints
246+
*/
247+
public function testValidateUniquenessWithNull(UniqueEntity $constraint)
248+
{
233249
$entity1 = new SingleIntIdEntity(1, null);
234250
$entity2 = new SingleIntIdEntity(2, null);
235251

@@ -242,15 +258,11 @@ public function testValidateUniquenessWithNull()
242258
$this->assertNoViolation();
243259
}
244260

245-
public function testValidateUniquenessWithIgnoreNullDisabled()
261+
/**
262+
* @dataProvider provideConstraintsWithIgnoreNullDisabled
263+
*/
264+
public function testValidateUniquenessWithIgnoreNullDisabled(UniqueEntity $constraint)
246265
{
247-
$constraint = new UniqueEntity([
248-
'message' => 'myMessage',
249-
'fields' => ['name', 'name2'],
250-
'em' => self::EM_NAME,
251-
'ignoreNull' => false,
252-
]);
253-
254266
$entity1 = new DoubleNameEntity(1, 'Foo', null);
255267
$entity2 = new DoubleNameEntity(2, 'Foo', null);
256268

@@ -276,30 +288,36 @@ public function testValidateUniquenessWithIgnoreNullDisabled()
276288
->assertRaised();
277289
}
278290

279-
public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgnoreNullEnabled()
291+
public function provideConstraintsWithIgnoreNullDisabled(): iterable
280292
{
281-
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
282-
$constraint = new UniqueEntity([
293+
yield 'Doctrine style' => [new UniqueEntity([
283294
'message' => 'myMessage',
284295
'fields' => ['name', 'name2'],
285296
'em' => self::EM_NAME,
286-
'ignoreNull' => true,
287-
]);
297+
'ignoreNull' => false,
298+
])];
288299

300+
if (\PHP_VERSION_ID >= 80000) {
301+
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name", "name2"], em: "foo", ignoreNull: false);')];
302+
}
303+
}
304+
305+
/**
306+
* @dataProvider provideConstraintsWithIgnoreNullEnabled
307+
*/
308+
public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgnoreNullEnabled(UniqueEntity $constraint)
309+
{
289310
$entity1 = new SingleIntIdEntity(1, null);
290311

312+
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
291313
$this->validator->validate($entity1, $constraint);
292314
}
293315

294-
public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored()
316+
/**
317+
* @dataProvider provideConstraintsWithIgnoreNullEnabled
318+
*/
319+
public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored(UniqueEntity $constraint)
295320
{
296-
$constraint = new UniqueEntity([
297-
'message' => 'myMessage',
298-
'fields' => ['name', 'name2'],
299-
'em' => self::EM_NAME,
300-
'ignoreNull' => true,
301-
]);
302-
303321
$entity1 = new DoubleNullableNameEntity(1, null, 'Foo');
304322
$entity2 = new DoubleNullableNameEntity(2, null, 'Foo');
305323

@@ -319,6 +337,20 @@ public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored()
319337
$this->assertNoViolation();
320338
}
321339

340+
public function provideConstraintsWithIgnoreNullEnabled(): iterable
341+
{
342+
yield 'Doctrine style' => [new UniqueEntity([
343+
'message' => 'myMessage',
344+
'fields' => ['name', 'name2'],
345+
'em' => self::EM_NAME,
346+
'ignoreNull' => true,
347+
])];
348+
349+
if (\PHP_VERSION_ID >= 80000) {
350+
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name", "name2"], em: "foo", ignoreNull: true);')];
351+
}
352+
}
353+
322354
public function testValidateUniquenessWithValidCustomErrorPath()
323355
{
324356
$constraint = new UniqueEntity([
@@ -353,15 +385,11 @@ public function testValidateUniquenessWithValidCustomErrorPath()
353385
->assertRaised();
354386
}
355387

356-
public function testValidateUniquenessUsingCustomRepositoryMethod()
388+
/**
389+
* @dataProvider provideConstraintsWithCustomRepositoryMethod
390+
*/
391+
public function testValidateUniquenessUsingCustomRepositoryMethod(UniqueEntity $constraint)
357392
{
358-
$constraint = new UniqueEntity([
359-
'message' => 'myMessage',
360-
'fields' => ['name'],
361-
'em' => self::EM_NAME,
362-
'repositoryMethod' => 'findByCustom',
363-
]);
364-
365393
$repository = $this->createRepositoryMock();
366394
$repository->expects($this->once())
367395
->method('findByCustom')
@@ -379,15 +407,11 @@ public function testValidateUniquenessUsingCustomRepositoryMethod()
379407
$this->assertNoViolation();
380408
}
381409

382-
public function testValidateUniquenessWithUnrewoundArray()
410+
/**
411+
* @dataProvider provideConstraintsWithCustomRepositoryMethod
412+
*/
413+
public function testValidateUniquenessWithUnrewoundArray(UniqueEntity $constraint)
383414
{
384-
$constraint = new UniqueEntity([
385-
'message' => 'myMessage',
386-
'fields' => ['name'],
387-
'em' => self::EM_NAME,
388-
'repositoryMethod' => 'findByCustom',
389-
]);
390-
391415
$entity = new SingleIntIdEntity(1, 'foo');
392416

393417
$repository = $this->createRepositoryMock();
@@ -414,6 +438,20 @@ function () use ($entity) {
414438
$this->assertNoViolation();
415439
}
416440

441+
public function provideConstraintsWithCustomRepositoryMethod(): iterable
442+
{
443+
yield 'Doctrine style' => [new UniqueEntity([
444+
'message' => 'myMessage',
445+
'fields' => ['name'],
446+
'em' => self::EM_NAME,
447+
'repositoryMethod' => 'findByCustom',
448+
])];
449+
450+
if (\PHP_VERSION_ID >= 80000) {
451+
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name"], em: "foo", repositoryMethod: "findByCustom");')];
452+
}
453+
}
454+
417455
/**
418456
* @dataProvider resultTypesProvider
419457
*/

src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*
2222
* @author Benjamin Eberlei <kontakt@beberlei.de>
2323
*/
24+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
2425
class UniqueEntity extends Constraint
2526
{
2627
const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f';
@@ -38,6 +39,41 @@ class UniqueEntity extends Constraint
3839
self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR',
3940
];
4041

42+
/**
43+
* {@inheritdoc}
44+
*
45+
* @param array|string $fields the combination of fields that must contain unique values or a set of options
46+
*/
47+
public function __construct(
48+
$fields,
49+
string $message = null,
50+
string $service = null,
51+
string $em = null,
52+
string $entityClass = null,
53+
string $repositoryMethod = null,
54+
string $errorPath = null,
55+
bool $ignoreNull = null,
56+
array $groups = null,
57+
$payload = null,
58+
array $options = []
59+
) {
60+
if (\is_array($fields) && \is_string(key($fields))) {
61+
$options = array_merge($fields, $options);
62+
} elseif (null !== $fields) {
63+
$options['fields'] = $fields;
64+
}
65+
66+
parent::__construct($options, $groups, $payload);
67+
68+
$this->message = $message ?? $this->message;
69+
$this->service = $service ?? $this->service;
70+
$this->em = $em ?? $this->em;
71+
$this->entityClass = $entityClass ?? $this->entityClass;
72+
$this->repositoryMethod = $repositoryMethod ?? $this->repositoryMethod;
73+
$this->errorPath = $errorPath ?? $this->errorPath;
74+
$this->ignoreNull = $ignoreNull ?? $this->ignoreNull;
75+
}
76+
4177
public function getRequiredOptions()
4278
{
4379
return ['fields'];

src/Symfony/Bridge/Doctrine/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"symfony/security-core": "^5.0",
4141
"symfony/expression-language": "^4.4|^5.0",
4242
"symfony/uid": "^5.1",
43-
"symfony/validator": "^5.0.2",
43+
"symfony/validator": "^5.2",
4444
"symfony/translation": "^4.4|^5.0",
4545
"symfony/var-dumper": "^4.4|^5.0",
4646
"doctrine/annotations": "~1.7",
@@ -61,7 +61,7 @@
6161
"symfony/property-info": "<5",
6262
"symfony/security-bundle": "<5",
6363
"symfony/security-core": "<5",
64-
"symfony/validator": "<5.0.2"
64+
"symfony/validator": "<5.2"
6565
},
6666
"suggest": {
6767
"symfony/form": "",

0 commit comments

Comments
 (0)
0