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..c60ceb1543e01 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueValidatorTest.php @@ -11,11 +11,12 @@ 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; 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 +97,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 +128,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 +145,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 +155,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."); @@ -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"; @@ -172,12 +200,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 +223,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,17 +270,25 @@ 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, '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.'); } @@ -267,7 +303,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 +311,7 @@ public function testValidateUniquenessUsingCustomRepositoryMethod() public function testValidateUniquenessWithUnrewoundArray() { - $entity = new SingleIntIdEntity(1, 'foo'); + $entity = new UniqueConstraintEntity(1, 'foo'); $entityManagerName = 'foo'; $repository = $this->createRepositoryMock(); @@ -309,7 +345,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 +405,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 +427,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); 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; }