From fc66683cf23b46107b18e15b8c5fb977817f1771 Mon Sep 17 00:00:00 2001 From: zenmate Date: Thu, 15 Mar 2018 23:48:03 +0200 Subject: [PATCH 1/2] Add UniqueCollection constraint and validator --- .../Validator/Constraints/Unique.php | 31 +++++++ .../Validator/Constraints/UniqueValidator.php | 53 +++++++++++ .../Tests/Constraints/UniqueValidatorTest.php | 89 +++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 src/Symfony/Component/Validator/Constraints/Unique.php create mode 100644 src/Symfony/Component/Validator/Constraints/UniqueValidator.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php diff --git a/src/Symfony/Component/Validator/Constraints/Unique.php b/src/Symfony/Component/Validator/Constraints/Unique.php new file mode 100644 index 0000000000..f1114bfc7c --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/Unique.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Yevgeniy Zholkevskiy + */ +class Unique extends Constraint +{ + const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a'; + + protected static $errorNames = array( + self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE', + ); + + public $message = 'This collection should contain only unique elements'; +} diff --git a/src/Symfony/Component/Validator/Constraints/UniqueValidator.php b/src/Symfony/Component/Validator/Constraints/UniqueValidator.php new file mode 100644 index 0000000000..67d480a38a --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/UniqueValidator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Yevgeniy Zholkevskiy + */ +class UniqueValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Unique) { + throw new UnexpectedTypeException($constraint, Unique::class); + } + + if (null === $value) { + return; + } + + if (!is_array($value) && !$value instanceof \IteratorAggregate) { + throw new UnexpectedTypeException($value, 'IteratorAggregate'); + } + + $collectionElements = array(); + foreach ($value as $element) { + if (in_array($element, $collectionElements, true)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Unique::IS_NOT_UNIQUE) + ->addViolation(); + + return; + } + $collectionElements[] = $element; + } + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php new file mode 100644 index 0000000000..c8c9b4188a --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use Symfony\Component\Validator\Constraints\Unique; +use Symfony\Component\Validator\Constraints\UniqueValidator; +use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; + +class UniqueValidatorTest extends ConstraintValidatorTestCase +{ + protected function createValidator() + { + return new UniqueValidator(); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException + */ + public function testExpectsUniqueConstraintCompatibleType() + { + $this->validator->validate('', new Unique()); + } + + /** + * @dataProvider getValidValues + */ + public function testValidValues($value) + { + $this->validator->validate($value, new Unique()); + + $this->assertNoViolation(); + } + + public function getValidValues() + { + return array( + yield 'null' => array(array(null)), + yield 'empty array' => array(array()), + yield 'single integer' => array(array(5)), + yield 'single string' => array(array('a')), + yield 'single object' => array(array(new \stdClass())), + yield 'unique booleans' => array(array(true, false)), + yield 'unique integers' => array(array(1, 2, 3, 4, 5, 6)), + yield 'unique floats' => array(array(0.1, 0.2, 0.3)), + yield 'unique strings' => array(array('a', 'b', 'c')), + yield 'unique arrays' => array(array(array(1, 2), array(2, 4), array(4, 6))), + yield 'unique objects' => array(array(new \stdClass(), new \stdClass())), + ); + } + + /** + * @dataProvider getInvalidValues + */ + public function testInvalidValues($value) + { + $constraint = new Unique(array( + 'message' => 'myMessage', + )); + $this->validator->validate($value, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', 'array') + ->setCode(Unique::IS_NOT_UNIQUE) + ->assertRaised(); + } + + public function getInvalidValues() + { + $object = new \stdClass(); + + return array( + yield 'not unique booleans' => array(array(true, true)), + yield 'not unique integers' => array(array(1, 2, 3, 3)), + yield 'not unique floats' => array(array(0.1, 0.2, 0.1)), + yield 'not unique string' => array(array('a', 'b', 'a')), + yield 'not unique arrays' => array(array(array(1, 1), array(2, 3), array(1, 1))), + yield 'not unique objects' => array(array($object, $object)), + ); + } +} From d0eb13e55a02375f6652715567080a6f5bdc0cce Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 13 Mar 2019 16:45:47 +0100 Subject: [PATCH 2/2] Rebase and update to latest CS --- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Validator/Constraints/Unique.php | 6 +-- .../Validator/Constraints/UniqueValidator.php | 10 ++-- .../Tests/Constraints/UniqueValidatorTest.php | 48 +++++++++---------- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 509f331767..54d6120a21 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * added UATP cards support to `CardSchemeValidator` * added option `allowNull` to NotBlank constraint * added `Json` constraint + * added `Unique` constraint 4.2.0 ----- diff --git a/src/Symfony/Component/Validator/Constraints/Unique.php b/src/Symfony/Component/Validator/Constraints/Unique.php index f1114bfc7c..391c4eaa13 100644 --- a/src/Symfony/Component/Validator/Constraints/Unique.php +++ b/src/Symfony/Component/Validator/Constraints/Unique.php @@ -21,11 +21,11 @@ use Symfony\Component\Validator\Constraint; */ class Unique extends Constraint { - const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a'; + public const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a'; - protected static $errorNames = array( + protected static $errorNames = [ self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE', - ); + ]; public $message = 'This collection should contain only unique elements'; } diff --git a/src/Symfony/Component/Validator/Constraints/UniqueValidator.php b/src/Symfony/Component/Validator/Constraints/UniqueValidator.php index 67d480a38a..72403b59bd 100644 --- a/src/Symfony/Component/Validator/Constraints/UniqueValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UniqueValidator.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; -use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * @author Yevgeniy Zholkevskiy @@ -33,13 +33,13 @@ class UniqueValidator extends ConstraintValidator return; } - if (!is_array($value) && !$value instanceof \IteratorAggregate) { - throw new UnexpectedTypeException($value, 'IteratorAggregate'); + if (!\is_array($value) && !$value instanceof \IteratorAggregate) { + throw new UnexpectedValueException($value, 'array|IteratorAggregate'); } - $collectionElements = array(); + $collectionElements = []; foreach ($value as $element) { - if (in_array($element, $collectionElements, true)) { + if (\in_array($element, $collectionElements, true)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $this->formatValue($value)) ->setCode(Unique::IS_NOT_UNIQUE) diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php index c8c9b4188a..65564abf9c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UniqueValidatorTest.php @@ -23,7 +23,7 @@ class UniqueValidatorTest extends ConstraintValidatorTestCase } /** - * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException + * @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException */ public function testExpectsUniqueConstraintCompatibleType() { @@ -42,19 +42,19 @@ class UniqueValidatorTest extends ConstraintValidatorTestCase public function getValidValues() { - return array( - yield 'null' => array(array(null)), - yield 'empty array' => array(array()), - yield 'single integer' => array(array(5)), - yield 'single string' => array(array('a')), - yield 'single object' => array(array(new \stdClass())), - yield 'unique booleans' => array(array(true, false)), - yield 'unique integers' => array(array(1, 2, 3, 4, 5, 6)), - yield 'unique floats' => array(array(0.1, 0.2, 0.3)), - yield 'unique strings' => array(array('a', 'b', 'c')), - yield 'unique arrays' => array(array(array(1, 2), array(2, 4), array(4, 6))), - yield 'unique objects' => array(array(new \stdClass(), new \stdClass())), - ); + return [ + yield 'null' => [[null]], + yield 'empty array' => [[]], + yield 'single integer' => [[5]], + yield 'single string' => [['a']], + yield 'single object' => [[new \stdClass()]], + yield 'unique booleans' => [[true, false]], + yield 'unique integers' => [[1, 2, 3, 4, 5, 6]], + yield 'unique floats' => [[0.1, 0.2, 0.3]], + yield 'unique strings' => [['a', 'b', 'c']], + yield 'unique arrays' => [[[1, 2], [2, 4], [4, 6]]], + yield 'unique objects' => [[new \stdClass(), new \stdClass()]], + ]; } /** @@ -62,9 +62,9 @@ class UniqueValidatorTest extends ConstraintValidatorTestCase */ public function testInvalidValues($value) { - $constraint = new Unique(array( + $constraint = new Unique([ 'message' => 'myMessage', - )); + ]); $this->validator->validate($value, $constraint); $this->buildViolation('myMessage') @@ -77,13 +77,13 @@ class UniqueValidatorTest extends ConstraintValidatorTestCase { $object = new \stdClass(); - return array( - yield 'not unique booleans' => array(array(true, true)), - yield 'not unique integers' => array(array(1, 2, 3, 3)), - yield 'not unique floats' => array(array(0.1, 0.2, 0.1)), - yield 'not unique string' => array(array('a', 'b', 'a')), - yield 'not unique arrays' => array(array(array(1, 1), array(2, 3), array(1, 1))), - yield 'not unique objects' => array(array($object, $object)), - ); + return [ + yield 'not unique booleans' => [[true, true]], + yield 'not unique integers' => [[1, 2, 3, 3]], + yield 'not unique floats' => [[0.1, 0.2, 0.1]], + yield 'not unique string' => [['a', 'b', 'a']], + yield 'not unique arrays' => [[[1, 1], [2, 3], [1, 1]]], + yield 'not unique objects' => [[$object, $object]], + ]; } }