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)), + ); + } +}