Enabled to use the UniqueEntity constraint as an attribute.
This commit is contained in:
parent
7b62f099c2
commit
5e7d3ab17b
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*/
|
||||
class UniqueEntityTest extends TestCase
|
||||
{
|
||||
public function testAttributeWithDefaultProperty()
|
||||
{
|
||||
$metadata = new ClassMetadata(UniqueEntityDummyOne::class);
|
||||
$loader = new AnnotationLoader();
|
||||
self::assertTrue($loader->loadClassMetadata($metadata));
|
||||
|
||||
/** @var UniqueEntity $constraint */
|
||||
list($constraint) = $metadata->getConstraints();
|
||||
self::assertSame(['email'], $constraint->fields);
|
||||
self::assertTrue($constraint->ignoreNull);
|
||||
self::assertSame('doctrine.orm.validator.unique', $constraint->validatedBy());
|
||||
self::assertSame(['Default', 'UniqueEntityDummyOne'], $constraint->groups);
|
||||
}
|
||||
|
||||
public function testAttributeWithCustomizedService()
|
||||
{
|
||||
$metadata = new ClassMetadata(UniqueEntityDummyTwo::class);
|
||||
$loader = new AnnotationLoader();
|
||||
self::assertTrue($loader->loadClassMetadata($metadata));
|
||||
|
||||
/** @var UniqueEntity $constraint */
|
||||
list($constraint) = $metadata->getConstraints();
|
||||
self::assertSame(['isbn'], $constraint->fields);
|
||||
self::assertSame('my_own_validator', $constraint->validatedBy());
|
||||
self::assertSame('my_own_entity_manager', $constraint->em);
|
||||
self::assertSame('App\Entity\MyEntity', $constraint->entityClass);
|
||||
self::assertSame('fetchDifferently', $constraint->repositoryMethod);
|
||||
}
|
||||
|
||||
public function testAttributeWithGroupsAndPaylod()
|
||||
{
|
||||
$metadata = new ClassMetadata(UniqueEntityDummyThree::class);
|
||||
$loader = new AnnotationLoader();
|
||||
self::assertTrue($loader->loadClassMetadata($metadata));
|
||||
|
||||
/** @var UniqueEntity $constraint */
|
||||
list($constraint) = $metadata->getConstraints();
|
||||
self::assertSame('uuid', $constraint->fields);
|
||||
self::assertSame('id', $constraint->errorPath);
|
||||
self::assertSame('some attached data', $constraint->payload);
|
||||
self::assertSame(['some_group'], $constraint->groups);
|
||||
}
|
||||
}
|
||||
|
||||
#[UniqueEntity(['email'], message: 'myMessage')]
|
||||
class UniqueEntityDummyOne
|
||||
{
|
||||
private $email;
|
||||
}
|
||||
|
||||
#[UniqueEntity(fields: ['isbn'], service: 'my_own_validator', em: 'my_own_entity_manager', entityClass: 'App\Entity\MyEntity', repositoryMethod: 'fetchDifferently')]
|
||||
class UniqueEntityDummyTwo
|
||||
{
|
||||
private $isbn;
|
||||
}
|
||||
|
||||
#[UniqueEntity('uuid', ignoreNull: false, errorPath: 'id', payload: 'some attached data', groups: ['some_group'])]
|
||||
class UniqueEntityDummyThree
|
||||
{
|
||||
private $id;
|
||||
private $uuid;
|
||||
}
|
@ -162,15 +162,11 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
|
||||
/**
|
||||
* This is a functional test as there is a large integration necessary to get the validator working.
|
||||
*
|
||||
* @dataProvider provideUniquenessConstraints
|
||||
*/
|
||||
public function testValidateUniqueness()
|
||||
public function testValidateUniqueness(UniqueEntity $constraint)
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name'],
|
||||
'em' => self::EM_NAME,
|
||||
]);
|
||||
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Foo');
|
||||
|
||||
@ -196,15 +192,24 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public function testValidateCustomErrorPath()
|
||||
public function provideUniquenessConstraints(): iterable
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
yield 'Doctrine style' => [new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name'],
|
||||
'em' => self::EM_NAME,
|
||||
'errorPath' => 'bar',
|
||||
]);
|
||||
])];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name"], em: "foo");')];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConstraintsWithCustomErrorPath
|
||||
*/
|
||||
public function testValidateCustomErrorPath(UniqueEntity $constraint)
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Foo');
|
||||
|
||||
@ -222,14 +227,25 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public function testValidateUniquenessWithNull()
|
||||
public function provideConstraintsWithCustomErrorPath(): iterable
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
yield 'Doctrine style' => [new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name'],
|
||||
'em' => self::EM_NAME,
|
||||
]);
|
||||
'errorPath' => 'bar',
|
||||
])];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name"], em: "foo", errorPath: "bar");')];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideUniquenessConstraints
|
||||
*/
|
||||
public function testValidateUniquenessWithNull(UniqueEntity $constraint)
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, null);
|
||||
$entity2 = new SingleIntIdEntity(2, null);
|
||||
|
||||
@ -242,15 +258,11 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testValidateUniquenessWithIgnoreNullDisabled()
|
||||
/**
|
||||
* @dataProvider provideConstraintsWithIgnoreNullDisabled
|
||||
*/
|
||||
public function testValidateUniquenessWithIgnoreNullDisabled(UniqueEntity $constraint)
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name', 'name2'],
|
||||
'em' => self::EM_NAME,
|
||||
'ignoreNull' => false,
|
||||
]);
|
||||
|
||||
$entity1 = new DoubleNameEntity(1, 'Foo', null);
|
||||
$entity2 = new DoubleNameEntity(2, 'Foo', null);
|
||||
|
||||
@ -276,30 +288,36 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgnoreNullEnabled()
|
||||
public function provideConstraintsWithIgnoreNullDisabled(): iterable
|
||||
{
|
||||
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
|
||||
$constraint = new UniqueEntity([
|
||||
yield 'Doctrine style' => [new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name', 'name2'],
|
||||
'em' => self::EM_NAME,
|
||||
'ignoreNull' => true,
|
||||
]);
|
||||
'ignoreNull' => false,
|
||||
])];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name", "name2"], em: "foo", ignoreNull: false);')];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideConstraintsWithIgnoreNullEnabled
|
||||
*/
|
||||
public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgnoreNullEnabled(UniqueEntity $constraint)
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, null);
|
||||
|
||||
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
|
||||
$this->validator->validate($entity1, $constraint);
|
||||
}
|
||||
|
||||
public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored()
|
||||
/**
|
||||
* @dataProvider provideConstraintsWithIgnoreNullEnabled
|
||||
*/
|
||||
public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored(UniqueEntity $constraint)
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name', 'name2'],
|
||||
'em' => self::EM_NAME,
|
||||
'ignoreNull' => true,
|
||||
]);
|
||||
|
||||
$entity1 = new DoubleNullableNameEntity(1, null, 'Foo');
|
||||
$entity2 = new DoubleNullableNameEntity(2, null, 'Foo');
|
||||
|
||||
@ -319,6 +337,20 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function provideConstraintsWithIgnoreNullEnabled(): iterable
|
||||
{
|
||||
yield 'Doctrine style' => [new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name', 'name2'],
|
||||
'em' => self::EM_NAME,
|
||||
'ignoreNull' => true,
|
||||
])];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name", "name2"], em: "foo", ignoreNull: true);')];
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateUniquenessWithValidCustomErrorPath()
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
@ -353,15 +385,11 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public function testValidateUniquenessUsingCustomRepositoryMethod()
|
||||
/**
|
||||
* @dataProvider provideConstraintsWithCustomRepositoryMethod
|
||||
*/
|
||||
public function testValidateUniquenessUsingCustomRepositoryMethod(UniqueEntity $constraint)
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name'],
|
||||
'em' => self::EM_NAME,
|
||||
'repositoryMethod' => 'findByCustom',
|
||||
]);
|
||||
|
||||
$repository = $this->createRepositoryMock();
|
||||
$repository->expects($this->once())
|
||||
->method('findByCustom')
|
||||
@ -379,15 +407,11 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testValidateUniquenessWithUnrewoundArray()
|
||||
/**
|
||||
* @dataProvider provideConstraintsWithCustomRepositoryMethod
|
||||
*/
|
||||
public function testValidateUniquenessWithUnrewoundArray(UniqueEntity $constraint)
|
||||
{
|
||||
$constraint = new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name'],
|
||||
'em' => self::EM_NAME,
|
||||
'repositoryMethod' => 'findByCustom',
|
||||
]);
|
||||
|
||||
$entity = new SingleIntIdEntity(1, 'foo');
|
||||
|
||||
$repository = $this->createRepositoryMock();
|
||||
@ -414,6 +438,20 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function provideConstraintsWithCustomRepositoryMethod(): iterable
|
||||
{
|
||||
yield 'Doctrine style' => [new UniqueEntity([
|
||||
'message' => 'myMessage',
|
||||
'fields' => ['name'],
|
||||
'em' => self::EM_NAME,
|
||||
'repositoryMethod' => 'findByCustom',
|
||||
])];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
yield 'Named arguments' => [eval('return new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(message: "myMessage", fields: ["name"], em: "foo", repositoryMethod: "findByCustom");')];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider resultTypesProvider
|
||||
*/
|
||||
|
@ -21,6 +21,7 @@ use Symfony\Component\Validator\Constraint;
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
|
||||
class UniqueEntity extends Constraint
|
||||
{
|
||||
const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f';
|
||||
@ -38,6 +39,41 @@ class UniqueEntity extends Constraint
|
||||
self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array|string $fields the combination of fields that must contain unique values or a set of options
|
||||
*/
|
||||
public function __construct(
|
||||
$fields,
|
||||
string $message = null,
|
||||
string $service = null,
|
||||
string $em = null,
|
||||
string $entityClass = null,
|
||||
string $repositoryMethod = null,
|
||||
string $errorPath = null,
|
||||
bool $ignoreNull = null,
|
||||
array $groups = null,
|
||||
$payload = null,
|
||||
array $options = []
|
||||
) {
|
||||
if (\is_array($fields) && \is_string(key($fields))) {
|
||||
$options = array_merge($fields, $options);
|
||||
} elseif (null !== $fields) {
|
||||
$options['fields'] = $fields;
|
||||
}
|
||||
|
||||
parent::__construct($options, $groups, $payload);
|
||||
|
||||
$this->message = $message ?? $this->message;
|
||||
$this->service = $service ?? $this->service;
|
||||
$this->em = $em ?? $this->em;
|
||||
$this->entityClass = $entityClass ?? $this->entityClass;
|
||||
$this->repositoryMethod = $repositoryMethod ?? $this->repositoryMethod;
|
||||
$this->errorPath = $errorPath ?? $this->errorPath;
|
||||
$this->ignoreNull = $ignoreNull ?? $this->ignoreNull;
|
||||
}
|
||||
|
||||
public function getRequiredOptions()
|
||||
{
|
||||
return ['fields'];
|
||||
|
@ -40,7 +40,7 @@
|
||||
"symfony/security-core": "^5.0",
|
||||
"symfony/expression-language": "^4.4|^5.0",
|
||||
"symfony/uid": "^5.1",
|
||||
"symfony/validator": "^5.0.2",
|
||||
"symfony/validator": "^5.2",
|
||||
"symfony/translation": "^4.4|^5.0",
|
||||
"symfony/var-dumper": "^4.4|^5.0",
|
||||
"doctrine/annotations": "~1.7",
|
||||
@ -61,7 +61,7 @@
|
||||
"symfony/property-info": "<5",
|
||||
"symfony/security-bundle": "<5",
|
||||
"symfony/security-core": "<5",
|
||||
"symfony/validator": "<5.0.2"
|
||||
"symfony/validator": "<5.2"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/form": "",
|
||||
|
Reference in New Issue
Block a user