From 4b4b13923038c9773e475cbc55952a1ff9607607 Mon Sep 17 00:00:00 2001 From: arvenil Date: Mon, 23 Dec 2013 15:16:02 +0100 Subject: [PATCH 1/4] Add UniqueConstraintEntity with unique constraint "name" so we can make more reliable tests. With this change testValidateUniquenessAfterConsideringMultipleQueryResults fails as it is broken by design. It adds to database duplicate value. --- .../Tests/Fixtures/AssociationEntity.php | 4 +- .../Tests/Fixtures/UniqueConstraintEntity.php | 42 +++++++++++++++++++ .../Constraints/UniqueValidatorTest.php | 34 +++++++-------- 3 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Fixtures/UniqueConstraintEntity.php diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php index 9a33435ff2eb3..d0ade9b2e5e95 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php @@ -26,8 +26,8 @@ class AssociationEntity private $id; /** - * @ORM\ManyToOne(targetEntity="SingleIntIdEntity") - * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity + * @ORM\ManyToOne(targetEntity="UniqueConstraintEntity") + * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\UniqueConstraintEntity */ public $single; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UniqueConstraintEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UniqueConstraintEntity.php new file mode 100644 index 0000000000000..ba735c0e84c5c --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UniqueConstraintEntity.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Table; +use Doctrine\ORM\Mapping\UniqueConstraint; + +/** + * @Entity + * @Table(uniqueConstraints={@UniqueConstraint(name="name", columns={"name"})}) + */ +class UniqueConstraintEntity +{ + /** @Id @Column(type="integer") */ + protected $id; + + /** @Column(type="string", nullable=true) */ + public $name; + + public function __construct($id, $name) + { + $this->id = $id; + $this->name = $name; + } + + public function __toString() + { + return (string) $this->name; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php index 92ad8caafbea2..3ce71b709edd7 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory; -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\UniqueConstraintEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; @@ -96,7 +96,7 @@ protected function createValidatorFactory($uniqueValidator) public function createValidator($entityManagerName, $em, $validateClass = null, $uniqueFields = null, $errorPath = null, $repositoryMethod = 'findBy', $ignoreNull = true) { if (!$validateClass) { - $validateClass = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'; + $validateClass = 'Symfony\Bridge\Doctrine\Tests\Fixtures\UniqueConstraintEntity'; } if (!$uniqueFields) { $uniqueFields = array('name'); @@ -127,7 +127,7 @@ private function createSchema($em) { $schemaTool = new SchemaTool($em); $schemaTool->createSchema(array( - $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'), + $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\UniqueConstraintEntity'), $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity'), $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity'), $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity'), @@ -144,7 +144,7 @@ public function testValidateUniqueness() $this->createSchema($em); $validator = $this->createValidator($entityManagerName, $em); - $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity1 = new UniqueConstraintEntity(1, 'Foo'); $violationsList = $validator->validate($entity1); $this->assertEquals(0, $violationsList->count(), "No violations found on entity before it is saved to the database."); @@ -154,7 +154,7 @@ public function testValidateUniqueness() $violationsList = $validator->validate($entity1); $this->assertEquals(0, $violationsList->count(), "No violations found on entity after it was saved to the database."); - $entity2 = new SingleIntIdEntity(2, 'Foo'); + $entity2 = new UniqueConstraintEntity(2, 'Foo'); $violationsList = $validator->validate($entity2); $this->assertEquals(1, $violationsList->count(), "Violation found on entity with conflicting entity existing in the database."); @@ -172,12 +172,12 @@ public function testValidateCustomErrorPath() $this->createSchema($em); $validator = $this->createValidator($entityManagerName, $em, null, null, 'bar'); - $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity1 = new UniqueConstraintEntity(1, 'Foo'); $em->persist($entity1); $em->flush(); - $entity2 = new SingleIntIdEntity(2, 'Foo'); + $entity2 = new UniqueConstraintEntity(2, 'Foo'); $violationsList = $validator->validate($entity2); $this->assertEquals(1, $violationsList->count(), "Violation found on entity with conflicting entity existing in the database."); @@ -195,8 +195,8 @@ public function testValidateUniquenessWithNull() $this->createSchema($em); $validator = $this->createValidator($entityManagerName, $em); - $entity1 = new SingleIntIdEntity(1, null); - $entity2 = new SingleIntIdEntity(2, null); + $entity1 = new UniqueConstraintEntity(1, null); + $entity2 = new UniqueConstraintEntity(2, null); $em->persist($entity1); $em->persist($entity2); @@ -242,8 +242,8 @@ public function testValidateUniquenessAfterConsideringMultipleQueryResults() $this->createSchema($em); $validator = $this->createValidator($entityManagerName, $em); - $entity1 = new SingleIntIdEntity(1, 'foo'); - $entity2 = new SingleIntIdEntity(2, 'foo'); + $entity1 = new UniqueConstraintEntity(1, 'foo'); + $entity2 = new UniqueConstraintEntity(2, 'foo'); $em->persist($entity1); $em->persist($entity2); @@ -267,7 +267,7 @@ public function testValidateUniquenessUsingCustomRepositoryMethod() $em = $this->createEntityManagerMock($repository); $validator = $this->createValidator($entityManagerName, $em, null, array(), null, 'findByCustom'); - $entity1 = new SingleIntIdEntity(1, 'foo'); + $entity1 = new UniqueConstraintEntity(1, 'foo'); $violationsList = $validator->validate($entity1); $this->assertEquals(0, $violationsList->count(), 'Violation is using custom repository method.'); @@ -275,7 +275,7 @@ public function testValidateUniquenessUsingCustomRepositoryMethod() public function testValidateUniquenessWithUnrewoundArray() { - $entity = new SingleIntIdEntity(1, 'foo'); + $entity = new UniqueConstraintEntity(1, 'foo'); $entityManagerName = 'foo'; $repository = $this->createRepositoryMock(); @@ -309,7 +309,7 @@ public function testAssociatedEntity() $this->createSchema($em); $validator = $this->createValidator($entityManagerName, $em, 'Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity', array('single')); - $entity1 = new SingleIntIdEntity(1, 'foo'); + $entity1 = new UniqueConstraintEntity(1, 'foo'); $associated = new AssociationEntity(); $associated->single = $entity1; @@ -369,7 +369,7 @@ public function testDedicatedEntityManagerNullObject() $uniqueValidator = new UniqueEntityValidator($registry); - $entity = new SingleIntIdEntity(1, null); + $entity = new UniqueConstraintEntity(1, null); $this->setExpectedException( 'Symfony\Component\Validator\Exception\ConstraintDefinitionException', @@ -391,11 +391,11 @@ public function testEntityManagerNullObject() $uniqueValidator = new UniqueEntityValidator($registry); - $entity = new SingleIntIdEntity(1, null); + $entity = new UniqueConstraintEntity(1, null); $this->setExpectedException( 'Symfony\Component\Validator\Exception\ConstraintDefinitionException', - 'Unable to find the object manager associated with an entity of class "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity"' + 'Unable to find the object manager associated with an entity of class "Symfony\Bridge\Doctrine\Tests\Fixtures\UniqueConstraintEntity"' ); $uniqueValidator->validate($entity, $constraint); From 958dfd7f0ec644f2af94b4dac84453e5678a4374 Mon Sep 17 00:00:00 2001 From: arvenil Date: Mon, 23 Dec 2013 15:20:07 +0100 Subject: [PATCH 2/4] Fix testValidateUniquenessAfterConsideringMultipleQueryResults --- .../Validator/Constraints/UniqueValidatorTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php index 3ce71b709edd7..25b2ffca36819 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php @@ -243,16 +243,24 @@ public function testValidateUniquenessAfterConsideringMultipleQueryResults() $validator = $this->createValidator($entityManagerName, $em); $entity1 = new UniqueConstraintEntity(1, 'foo'); - $entity2 = new UniqueConstraintEntity(2, 'foo'); + $entity2 = new UniqueConstraintEntity(2, 'bar'); + $entity3 = new UniqueConstraintEntity(3, 'foo'); + $entity4 = new UniqueConstraintEntity(4, 'bar'); $em->persist($entity1); $em->persist($entity2); $em->flush(); $violationsList = $validator->validate($entity1); - $this->assertEquals(1, $violationsList->count(), 'Violation found on entity with conflicting entity existing in the database.'); + $this->assertEquals(0, $violationsList->count(), 'No violations found on entity after it was saved to the database.'); $violationsList = $validator->validate($entity2); + $this->assertEquals(0, $violationsList->count(), 'No violations found on entity after it was saved to the database.'); + + $violationsList = $validator->validate($entity3); + $this->assertEquals(1, $violationsList->count(), 'Violation found on entity with conflicting entity existing in the database.'); + + $violationsList = $validator->validate($entity4); $this->assertEquals(1, $violationsList->count(), 'Violation found on entity with conflicting entity existing in the database.'); } From d7744ee5c411785bf7d00c7a1a818ccb9bd18993 Mon Sep 17 00:00:00 2001 From: arvenil Date: Mon, 23 Dec 2013 15:35:41 +0100 Subject: [PATCH 3/4] Add test for bug when collection has same value listed twice --- .../Constraints/UniqueValidatorTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php index 25b2ffca36819..c60ceb1543e01 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints; +use Doctrine\DBAL\DBALException; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; use Symfony\Component\Validator\DefaultTranslator; @@ -165,6 +166,33 @@ public function testValidateUniqueness() $this->assertEquals('Foo', $violation->getInvalidValue()); } + public function testValidateUniquenessForDuplicatesInCollection() + { + $entityManagerName = "foo"; + $em = DoctrineTestHelper::createTestEntityManager(); + $this->createSchema($em); + $validator = $this->createValidator($entityManagerName, $em); + + $entity1 = new UniqueConstraintEntity(1, 'Foo'); + $violationsList = $validator->validate($entity1); + $this->assertEquals(0, $violationsList->count(), "No violations found on entity before it is saved to the database."); + + $entity2 = new UniqueConstraintEntity(2, 'Foo'); + $violationsList = $validator->validate($entity2); + $this->assertEquals(1, $violationsList->count(), "Violation found on entity with conflicting entity existing in collection."); + + // Check if database really fails with error + // SQLSTATE[23000]: Integrity constraint violation: 19 column name is not unique + $em->persist($entity1); + $em->persist($entity2); + try { + $em->flush(); + $this->fail("Duplicate entity was successfully saved"); + } catch (DBALException $e) { + return; + } + } + public function testValidateCustomErrorPath() { $entityManagerName = "foo"; From 7f6c57e2a134efbbfce115687fe46fb05517c453 Mon Sep 17 00:00:00 2001 From: arvenil Date: Mon, 23 Dec 2013 16:56:07 +0100 Subject: [PATCH 4/4] Fix for bug when new collection had same item listed twice --- .../Validator/Constraints/UniqueEntityValidator.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 7f83da4ac6429..fff091745d8ff 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -29,6 +29,11 @@ class UniqueEntityValidator extends ConstraintValidator */ private $registry; + /** + * @var array + */ + protected $collection = array(); + /** * @param ManagerRegistry $registry */ @@ -122,11 +127,17 @@ public function validate($entity, Constraint $constraint) reset($result); } + $class = get_class($entity); + $constraintKey = md5(serialize($fields)); + $valuesKey = md5(serialize($criteria)); /* If no entity matched the query criteria or a single entity matched, * which is the same as the entity being validated, the criteria is * unique. */ - if (0 === count($result) || (1 === count($result) && $entity === ($result instanceof \Iterator ? $result->current() : current($result)))) { + if (0 === count($result) && !isset($this->collection[$class][$constraintKey][$valuesKey])) { + $this->collection[$class][$constraintKey][$valuesKey] = true; + return; + } elseif (1 === count($result) && $entity === ($result instanceof \Iterator ? $result->current() : current($result))) { return; }