[DoctrineMongoDBBundle] added unique constraint, validator and test, registered validator in DIC

This commit is contained in:
Bulat Shakirzyanov 2011-01-19 13:02:48 -05:00 committed by Fabien Potencier
parent 84fa4b50db
commit 1cbd0caa89
5 changed files with 336 additions and 0 deletions

View File

@ -51,6 +51,9 @@
<!-- security/user -->
<parameter key="security.user.provider.document.class">Symfony\Bundle\DoctrineMongoDBBundle\Security\DocumentUserProvider</parameter>
<!-- validator -->
<parameter key="doctrine_odm.mongodb.validator.unique.class">Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\DoctrineMongoDBUniqueValidator</parameter>
</parameters>
<services>
@ -86,6 +89,11 @@
</service>
<service id="security.user.document_manager" alias="doctrine.odm.mongodb.default_document_manager" />
<!-- validator -->
<service id="doctrine_odm.mongodb.validator.unique" class="%doctrine_odm.mongodb.validator.unique.class%">
<argument type="service" id="doctrine.odm.mongodb.document_manager" />
</service>
</services>
</container>

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Bundle\DoctrineMongoDBBundle\Tests\Fixtures\Validator;
class Document
{
public $id;
public $unique;
}

View File

@ -0,0 +1,143 @@
<?php
namespace Symfony\Bundle\DoctrineMongoDBBundle\Tests\Validator\Constraints;
use Symfony\Bundle\DoctrineMongoDBBundle\Tests\Fixtures\Validator\Document;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\DocumentRepository;
use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\DoctrineMongoDBUnique;
use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\DoctrineMongoDBUniqueValidator;
class DoctrineMongoDBUniqueValidatorTest extends \PHPUnit_Framework_TestCase
{
private $dm;
private $repository;
private $validator;
private $classMetadata;
private $uniqueFieldName = 'unique';
public function setUp()
{
$this->classMetadata = $this->getClassMetadata();
$this->repository = $this->getDocumentRepository();
$this->dm = $this->getDocumentManager($this->classMetadata, $this->repository);
$this->validator = new DoctrineMongoDBUniqueValidator($this->dm);
}
public function tearDown()
{
unset($this->validator, $this->dm, $this->repository, $this->classMetadata);
}
/**
* @dataProvider getFieldsPathsValuesDocumentsAndReturns
*/
public function testShouldValidateValidStringMappingValues($field, $path, $value, $document, $return)
{
$this->setFieldMapping($field, 'string');
$this->repository->expects($this->once())
->method('findOneBy')
->with(array($path => $value))
->will($this->returnValue($return));
$this->assertTrue($this->validator->isValid($document, new DoctrineMongoDBUnique($path)));
}
public function getFieldsPathsValuesDocumentsAndReturns()
{
$field = 'unique';
$path = $field;
$value = 'someUniqueValueToBeValidated';
$document = $this->getFixtureDocument($field, $value);
return array(
array('unique', 'unique', 'someUniqueValueToBeValidated', $document, null),
array('unique', 'unique', 'someUniqueValueToBeValidated', $document, $document),
array('unique', 'unique', 'someUniqueValueToBeValidated', $document, $this->getFixtureDocument($field, $value)),
);
}
/**
* @dataProvider getFieldTypesFieldsPathsValuesAndQueries
*/
public function testGetsCorrectQueryArrayForCollection($type, $field, $path, $value, $query)
{
$this->setFieldMapping($field, $type);
$document = $this->getFixtureDocument($field, $value);
$this->repository->expects($this->once())
->method('findOneBy')
->with($query);
$this->validator->isValid($document, new DoctrineMongoDBUnique($path));
}
public function getFieldTypesFieldsPathsValuesAndQueries()
{
$field = 'unique';
$key = 'uniqueValue';
$path = $field.'.'.$key;
$value = 'someUniqueValueToBeValidated';
return array(
array('collection', $field, $path, array($value), array($field => array('$in' => array($value)))),
array('hash', $field, $path, array($key => $value), array($path => $value)),
);
}
protected function getDocumentManager(ClassMetadata $classMetadata, DocumentRepository $repository)
{
$dm = $this->getMockBuilder('Doctrine\ODM\MongoDB\DocumentManager')
->disableOriginalConstructor()
->setMethods(array('getClassMetadata', 'getRepository'))
->getMock();
$dm->expects($this->any())
->method('getClassMetadata')
->will($this->returnValue($classMetadata));
$dm->expects($this->any())
->method('getRepository')
->will($this->returnValue($repository));
return $dm;
}
protected function getDocumentRepository()
{
$dm = $this->getMock('Doctrine\ODM\MongoDB\DocumentRepository', array('findOneBy'), array(), '', false, false);
return $dm;
}
protected function getClassMetadata()
{
$classMetadata = $this->getMock('Doctrine\ODM\MongoDB\Mapping\ClassMetadata', array(), array(), '', false, false);
$classMetadata->expects($this->any())
->method('getFieldValue')
->will($this->returnCallback(function($document, $fieldName) {
return $document->{$fieldName};
}));
$classMetadata->fieldmappings = array();
return $classMetadata;
}
protected function setFieldMapping($fieldName, $type, array $attributes = array())
{
$this->classMetadata->fieldMappings[$fieldName] = array_merge(array(
'fieldName' => $fieldName,
'type' => $type,
), $attributes);
}
protected function getFixtureDocument($field, $value, $id = 1)
{
$document = new Document();
$document->{$field} = $value;
$document->id = 1;
return $document;
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* Doctrine MongoDB ODM unique value constraint.
*
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
class DoctrineMongoDBUnique extends Constraint
{
public $message = 'The value for {{ property }} already exists.';
public $path;
public function defaultOption()
{
return 'path';
}
public function requiredOptions()
{
return array('path');
}
public function validatedBy()
{
return 'doctrine_odm.mongodb.validator.unique';
}
public function targets()
{
return Constraint::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,131 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Proxy\Proxy;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Doctrine MongoDB ODM unique value validator.
*
* @author Bulat Shakirzyanov <bulat@theopenskyproject.com>
*/
class DoctrineMongoDBUniqueValidator extends ConstraintValidator
{
protected $dm;
public function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
/**
* @param Doctrine\ODM\MongoDB\Document $value
* @param Constraint $constraint
* @return bool
*/
public function isValid($document, Constraint $constraint)
{
$class = get_class($document);
$metadata = $this->dm->getClassMetadata($class);
if ($metadata->isEmbeddedDocument) {
throw new \InvalidArgumentException(sprintf("Document '%s' is an embedded document, and cannot be validated", $class));
}
$query = $this->getQueryArray($metadata, $document, $constraint->path);
// check if document exists in mongodb
if (null === ($doc = $this->dm->getRepository($class)->findOneBy($query))) {
return true;
}
// check if document in mongodb is the same document as the checked one
if ($doc === $document) {
return true;
}
// check if returned document is proxy and initialize the minimum identifier if needed
if ($doc instanceof Proxy) {
$metadata->setIdentifierValue($doc, $doc->__identifier);
}
// check if document has the same identifier as the current one
if ($metadata->getIdentifierValue($doc) === $metadata->getIdentifierValue($document)) {
return true;
}
$this->context->setPropertyPath($this->context->getPropertyPath() . '.' . $constraint->path);
$this->setMessage($constraint->message, array(
'{{ property }}' => $constraint->path,
));
return false;
}
protected function getQueryArray(ClassMetadata $metadata, $document, $path)
{
$class = $metadata->name;
$field = $this->getFieldNameFromPropertyPath($path);
if (!isset($metadata->fieldMappings[$field])) {
throw new \LogicException('Mapping for \'' . $path . '\' doesn\'t exist for ' . $class);
}
$mapping = $metadata->fieldMappings[$field];
if (isset($mapping['reference']) && $mapping['reference']) {
throw new \LogicException('Cannot determine uniqueness of referenced document values');
}
switch ($mapping['type']) {
case 'one':
// TODO: implement support for embed one documents
case 'many':
// TODO: implement support for embed many documents
throw new \RuntimeException('Not Implemented.');
case 'hash':
$value = $metadata->getFieldValue($document, $mapping['fieldName']);
return array($path => $this->getFieldValueRecursively($path, $value));
case 'collection':
return array($mapping['fieldName'] => array('$in' => $metadata->getFieldValue($document, $mapping['fieldName'])));
default:
return array($mapping['fieldName'] => $metadata->getFieldValue($document, $mapping['fieldName']));
}
}
/**
* Returns the actual document field value
*
* E.g. document.someVal -> document
* user.emails -> user
* username -> username
*
* @param string $field
* @return string
*/
protected function getFieldNameFromPropertyPath($field)
{
$pieces = explode('.', $field);
return $pieces[0];
}
protected function getFieldValueRecursively($fieldName, $value)
{
$pieces = explode('.', $fieldName);
unset($pieces[0]);
foreach ($pieces as $piece) {
$value = $value[$piece];
}
return $value;
}
}