Thanks to visit codestin.com
Credit goes to github.com

Skip to content

UniqueEntity failed to invalidate new collections with duplicate values #9848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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'),
Expand All @@ -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.");

Expand All @@ -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.");
Expand All @@ -165,19 +166,46 @@ 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";
$em = DoctrineTestHelper::createTestEntityManager();
$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.");
Expand All @@ -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);
Expand Down Expand Up @@ -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.');
}

Expand All @@ -267,15 +303,15 @@ 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.');
}

public function testValidateUniquenessWithUnrewoundArray()
{
$entity = new SingleIntIdEntity(1, 'foo');
$entity = new UniqueConstraintEntity(1, 'foo');

$entityManagerName = 'foo';
$repository = $this->createRepositoryMock();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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',
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class UniqueEntityValidator extends ConstraintValidator
*/
private $registry;

/**
* @var array
*/
protected $collection = array();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing the currently persisted entities is the responsibility of the entity manager. Duplicating this here is 1) erroneous (is each entity persisted? What about deleted entities? etc.) and 2) a memory leak (we only add, but never remove entities from the collection).


/**
* @param ManagerRegistry $registry
*/
Expand Down Expand Up @@ -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;
}

Expand Down