Skip to content

Commit

Permalink
[Doctrine] Implement suggested changes by Stof, added functional test…
Browse files Browse the repository at this point in the history
… to verify unique validator works.
  • Loading branch information
beberlei committed May 18, 2011
1 parent cfc2471 commit 8ff1d09
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 51 deletions.
Expand Up @@ -9,18 +9,38 @@
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Validator;
namespace Symfony\Bridge\Doctrine\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

class UniqueEntity extends \Symfony\Component\Validator\Constraint
/**
* Constraint for the Unique Entity validator
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class UniqueEntity extends Constraint
{
public $message = 'This value is already used.';
public $entityManagerName = false;
public $em = false;
public $fields = array();

public function getRequiredOptions()
{
return array('fields');
}

/**
* The validator must be defined as a service with this name.
*
* @return string
*/
public function validatedBy()
{
return 'doctrine.orm.validator.unique';
}

/**
* {@inheritDoc}
*/
Expand Down
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Validator\Constraints;

use Symfony\Bundle\DoctrineBundle\Registry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\ConstraintValidator;

/**
* Unique Entity Validator checks if one or a set of fields contain unique values.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class UniqueEntityValidator extends ConstraintValidator
{
/**
* @var Registry
*/
private $registry;

/**
* @param Registry $registry
*/
public function __construct(Registry $registry)
{
$this->registry = $registry;
}

/**
* @param object $entity
* @param Constraint $constraint
* @return bool
*/
public function isValid($entity, Constraint $constraint)
{
if (!is_array($constraint->fields)) {
throw new UnexpectedTypeException($constraint->fields, 'array');
}
if (count($constraint->fields) == 0) {
throw new ConstraintDefinitionException("At least one field has to specified.");
}

$em = $this->registry->getEntityManager($constraint->em);

$className = $this->context->getCurrentClass();
$class = $em->getClassMetadata($className);

$criteria = array();
foreach ($constraint->fields as $fieldName) {
if (!isset($class->reflFields[$fieldName])) {
throw new ConstraintDefinitionException("Only field names mapped by Doctrine can be validated for uniqueness.");
}

$criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity);
}

$repository = $em->getRepository($className);
$result = $repository->findBy($criteria);

if (count($result) > 0 && $result[0] !== $entity) {
$oldPath = $this->context->getPropertyPath();
$this->context->setPropertyPath( empty($oldPath) ? $constraint->fields[0] : $oldPath . "." . $constraint->fields[0]);
$this->context->addViolation($constraint->message, array(), $criteria[$constraint->fields[0]]);
$this->context->setPropertyPath($oldPath);
}

return true; // all true, we added the violation already!
}
}
48 changes: 0 additions & 48 deletions src/Symfony/Bridge/Doctrine/Validator/UniqueEntityValidator.php

This file was deleted.

@@ -0,0 +1,103 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Tests\Bridge\Doctrine\Validator\Constraints;

require_once __DIR__.'/../../Form/DoctrineOrmTestCase.php';
require_once __DIR__.'/../../Fixtures/SingleIdentEntity.php';

use Symfony\Tests\Bridge\Doctrine\Form\DoctrineOrmTestCase;
use Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Validator;
use Doctrine\ORM\Tools\SchemaTool;

class UniqueValidatorTest extends DoctrineOrmTestCase
{
protected function createRegistryMock($entityManagerName, $em)
{
$registry = $this->getMock('Symfony\Bundle\DoctrineBundle\Registry', array(), array(), '', false);
$registry->expects($this->any())
->method('getEntityManager')
->with($this->equalTo($entityManagerName))
->will($this->returnValue($em));
return $registry;
}

protected function createMetadataFactoryMock($metadata)
{
$metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
$metadataFactory->expects($this->any())
->method('getClassMetadata')
->with($this->equalTo($metadata->name))
->will($this->returnValue($metadata));
return $metadataFactory;
}

protected function createValidatorFactory($uniqueValidator)
{
$validatorFactory = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface');
$validatorFactory->expects($this->any())
->method('getInstance')
->with($this->isInstanceOf('Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity'))
->will($this->returnValue($uniqueValidator));
return $validatorFactory;
}

/**
* This is a functinoal test as there is a large integration necessary to get the validator working.
*/
public function testValidateUniqueness()
{
$entityManagerName = "foo";
$em = $this->createTestEntityManager();
$schemaTool = new SchemaTool($em);
$schemaTool->createSchema(array(
$em->getClassMetadata('Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity')
));

$entity1 = new SingleIdentEntity(1, 'Foo');

$registry = $this->createRegistryMock($entityManagerName, $em);

$uniqueValidator = new UniqueEntityValidator($registry);

$metadata = new ClassMetadata('Symfony\Tests\Bridge\Doctrine\Form\Fixtures\SingleIdentEntity');
$metadata->addConstraint(new UniqueEntity(array('fields' => array('name'), 'em' => $entityManagerName)));

$metadataFactory = $this->createMetadataFactoryMock($metadata);
$validatorFactory = $this->createValidatorFactory($uniqueValidator);

$validator = new Validator($metadataFactory, $validatorFactory);

$violationsList = $validator->validate($entity1);
$this->assertEquals(0, $violationsList->count(), "No violations found on entity before it is saved to the database.");

$em->persist($entity1);
$em->flush();

$violationsList = $validator->validate($entity1);
$this->assertEquals(0, $violationsList->count(), "No violations found on entity after it was saved to the database.");

$entity2 = new SingleIdentEntity(2, 'Foo');

$violationsList = $validator->validate($entity2);
$this->assertEquals(1, $violationsList->count(), "No violations found on entity after it was saved to the database.");

$violation = $violationsList[0];
$this->assertEquals('This value is already used.', $violation->getMessage());
$this->assertEquals('name', $violation->getPropertyPath());
$this->assertEquals('Foo', $violation->getInvalidValue());
}
}

0 comments on commit 8ff1d09

Please sign in to comment.