Skip to content

Commit 99a7900

Browse files
avalanche123fabpot
authored andcommitted
[DoctrineMongoDBBundle] added unique constraint, validator and test, registered validator in DIC
1 parent 779dad6 commit 99a7900

File tree

5 files changed

+336
-0
lines changed

5 files changed

+336
-0
lines changed

Resources/config/mongodb.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151

5252
<!-- security/user -->
5353
<parameter key="security.user.provider.document.class">Symfony\Bundle\DoctrineMongoDBBundle\Security\DocumentUserProvider</parameter>
54+
55+
<!-- validator -->
56+
<parameter key="doctrine_odm.mongodb.validator.unique.class">Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\DoctrineMongoDBUniqueValidator</parameter>
5457
</parameters>
5558

5659
<services>
@@ -86,6 +89,11 @@
8689
</service>
8790

8891
<service id="security.user.document_manager" alias="doctrine.odm.mongodb.default_document_manager" />
92+
93+
<!-- validator -->
94+
<service id="doctrine_odm.mongodb.validator.unique" class="%doctrine_odm.mongodb.validator.unique.class%">
95+
<argument type="service" id="doctrine.odm.mongodb.document_manager" />
96+
</service>
8997

9098
</services>
9199
</container>

Tests/Fixtures/Validator/Document.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\DoctrineMongoDBBundle\Tests\Fixtures\Validator;
4+
5+
class Document
6+
{
7+
public $id;
8+
public $unique;
9+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\DoctrineMongoDBBundle\Tests\Validator\Constraints;
4+
5+
use Symfony\Bundle\DoctrineMongoDBBundle\Tests\Fixtures\Validator\Document;
6+
7+
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
8+
use Doctrine\ODM\MongoDB\DocumentRepository;
9+
use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\DoctrineMongoDBUnique;
10+
use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\DoctrineMongoDBUniqueValidator;
11+
12+
class DoctrineMongoDBUniqueValidatorTest extends \PHPUnit_Framework_TestCase
13+
{
14+
private $dm;
15+
private $repository;
16+
private $validator;
17+
private $classMetadata;
18+
private $uniqueFieldName = 'unique';
19+
20+
public function setUp()
21+
{
22+
$this->classMetadata = $this->getClassMetadata();
23+
$this->repository = $this->getDocumentRepository();
24+
$this->dm = $this->getDocumentManager($this->classMetadata, $this->repository);
25+
$this->validator = new DoctrineMongoDBUniqueValidator($this->dm);
26+
}
27+
28+
public function tearDown()
29+
{
30+
unset($this->validator, $this->dm, $this->repository, $this->classMetadata);
31+
}
32+
33+
/**
34+
* @dataProvider getFieldsPathsValuesDocumentsAndReturns
35+
*/
36+
public function testShouldValidateValidStringMappingValues($field, $path, $value, $document, $return)
37+
{
38+
$this->setFieldMapping($field, 'string');
39+
40+
$this->repository->expects($this->once())
41+
->method('findOneBy')
42+
->with(array($path => $value))
43+
->will($this->returnValue($return));
44+
45+
$this->assertTrue($this->validator->isValid($document, new DoctrineMongoDBUnique($path)));
46+
}
47+
48+
public function getFieldsPathsValuesDocumentsAndReturns()
49+
{
50+
$field = 'unique';
51+
$path = $field;
52+
$value = 'someUniqueValueToBeValidated';
53+
$document = $this->getFixtureDocument($field, $value);
54+
55+
return array(
56+
array('unique', 'unique', 'someUniqueValueToBeValidated', $document, null),
57+
array('unique', 'unique', 'someUniqueValueToBeValidated', $document, $document),
58+
array('unique', 'unique', 'someUniqueValueToBeValidated', $document, $this->getFixtureDocument($field, $value)),
59+
);
60+
}
61+
62+
/**
63+
* @dataProvider getFieldTypesFieldsPathsValuesAndQueries
64+
*/
65+
public function testGetsCorrectQueryArrayForCollection($type, $field, $path, $value, $query)
66+
{
67+
$this->setFieldMapping($field, $type);
68+
$document = $this->getFixtureDocument($field, $value);
69+
70+
$this->repository->expects($this->once())
71+
->method('findOneBy')
72+
->with($query);
73+
74+
$this->validator->isValid($document, new DoctrineMongoDBUnique($path));
75+
}
76+
77+
public function getFieldTypesFieldsPathsValuesAndQueries()
78+
{
79+
$field = 'unique';
80+
$key = 'uniqueValue';
81+
$path = $field.'.'.$key;
82+
$value = 'someUniqueValueToBeValidated';
83+
84+
return array(
85+
array('collection', $field, $path, array($value), array($field => array('$in' => array($value)))),
86+
array('hash', $field, $path, array($key => $value), array($path => $value)),
87+
);
88+
}
89+
90+
protected function getDocumentManager(ClassMetadata $classMetadata, DocumentRepository $repository)
91+
{
92+
$dm = $this->getMockBuilder('Doctrine\ODM\MongoDB\DocumentManager')
93+
->disableOriginalConstructor()
94+
->setMethods(array('getClassMetadata', 'getRepository'))
95+
->getMock();
96+
$dm->expects($this->any())
97+
->method('getClassMetadata')
98+
->will($this->returnValue($classMetadata));
99+
$dm->expects($this->any())
100+
->method('getRepository')
101+
->will($this->returnValue($repository));
102+
103+
return $dm;
104+
}
105+
106+
protected function getDocumentRepository()
107+
{
108+
$dm = $this->getMock('Doctrine\ODM\MongoDB\DocumentRepository', array('findOneBy'), array(), '', false, false);
109+
110+
return $dm;
111+
}
112+
113+
protected function getClassMetadata()
114+
{
115+
$classMetadata = $this->getMock('Doctrine\ODM\MongoDB\Mapping\ClassMetadata', array(), array(), '', false, false);
116+
$classMetadata->expects($this->any())
117+
->method('getFieldValue')
118+
->will($this->returnCallback(function($document, $fieldName) {
119+
return $document->{$fieldName};
120+
}));
121+
122+
$classMetadata->fieldmappings = array();
123+
124+
return $classMetadata;
125+
}
126+
127+
protected function setFieldMapping($fieldName, $type, array $attributes = array())
128+
{
129+
$this->classMetadata->fieldMappings[$fieldName] = array_merge(array(
130+
'fieldName' => $fieldName,
131+
'type' => $type,
132+
), $attributes);
133+
}
134+
135+
protected function getFixtureDocument($field, $value, $id = 1)
136+
{
137+
$document = new Document();
138+
$document->{$field} = $value;
139+
$document->id = 1;
140+
141+
return $document;
142+
}
143+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
16+
/**
17+
* Doctrine MongoDB ODM unique value constraint.
18+
*
19+
* @author Bulat Shakirzyanov <[email protected]>
20+
*/
21+
class DoctrineMongoDBUnique extends Constraint
22+
{
23+
public $message = 'The value for {{ property }} already exists.';
24+
public $path;
25+
26+
public function defaultOption()
27+
{
28+
return 'path';
29+
}
30+
31+
public function requiredOptions()
32+
{
33+
return array('path');
34+
}
35+
36+
public function validatedBy()
37+
{
38+
return 'doctrine_odm.mongodb.validator.unique';
39+
}
40+
41+
public function targets()
42+
{
43+
return Constraint::CLASS_CONSTRAINT;
44+
}
45+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints;
13+
14+
use Doctrine\ODM\MongoDB\DocumentManager;
15+
use Doctrine\ODM\MongoDB\Proxy\Proxy;
16+
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
17+
use Symfony\Component\Validator\Constraint;
18+
use Symfony\Component\Validator\ConstraintValidator;
19+
20+
/**
21+
* Doctrine MongoDB ODM unique value validator.
22+
*
23+
* @author Bulat Shakirzyanov <[email protected]>
24+
*/
25+
class DoctrineMongoDBUniqueValidator extends ConstraintValidator
26+
{
27+
28+
protected $dm;
29+
30+
public function __construct(DocumentManager $dm)
31+
{
32+
$this->dm = $dm;
33+
}
34+
35+
/**
36+
* @param Doctrine\ODM\MongoDB\Document $value
37+
* @param Constraint $constraint
38+
* @return bool
39+
*/
40+
public function isValid($document, Constraint $constraint)
41+
{
42+
$class = get_class($document);
43+
$metadata = $this->dm->getClassMetadata($class);
44+
45+
if ($metadata->isEmbeddedDocument) {
46+
throw new \InvalidArgumentException(sprintf("Document '%s' is an embedded document, and cannot be validated", $class));
47+
}
48+
49+
$query = $this->getQueryArray($metadata, $document, $constraint->path);
50+
51+
// check if document exists in mongodb
52+
if (null === ($doc = $this->dm->getRepository($class)->findOneBy($query))) {
53+
return true;
54+
}
55+
56+
// check if document in mongodb is the same document as the checked one
57+
if ($doc === $document) {
58+
return true;
59+
}
60+
61+
// check if returned document is proxy and initialize the minimum identifier if needed
62+
if ($doc instanceof Proxy) {
63+
$metadata->setIdentifierValue($doc, $doc->__identifier);
64+
}
65+
66+
// check if document has the same identifier as the current one
67+
if ($metadata->getIdentifierValue($doc) === $metadata->getIdentifierValue($document)) {
68+
return true;
69+
}
70+
71+
$this->context->setPropertyPath($this->context->getPropertyPath() . '.' . $constraint->path);
72+
$this->setMessage($constraint->message, array(
73+
'{{ property }}' => $constraint->path,
74+
));
75+
return false;
76+
}
77+
78+
protected function getQueryArray(ClassMetadata $metadata, $document, $path)
79+
{
80+
$class = $metadata->name;
81+
$field = $this->getFieldNameFromPropertyPath($path);
82+
if (!isset($metadata->fieldMappings[$field])) {
83+
throw new \LogicException('Mapping for \'' . $path . '\' doesn\'t exist for ' . $class);
84+
}
85+
$mapping = $metadata->fieldMappings[$field];
86+
if (isset($mapping['reference']) && $mapping['reference']) {
87+
throw new \LogicException('Cannot determine uniqueness of referenced document values');
88+
}
89+
switch ($mapping['type']) {
90+
case 'one':
91+
// TODO: implement support for embed one documents
92+
case 'many':
93+
// TODO: implement support for embed many documents
94+
throw new \RuntimeException('Not Implemented.');
95+
case 'hash':
96+
$value = $metadata->getFieldValue($document, $mapping['fieldName']);
97+
return array($path => $this->getFieldValueRecursively($path, $value));
98+
case 'collection':
99+
return array($mapping['fieldName'] => array('$in' => $metadata->getFieldValue($document, $mapping['fieldName'])));
100+
default:
101+
return array($mapping['fieldName'] => $metadata->getFieldValue($document, $mapping['fieldName']));
102+
}
103+
}
104+
105+
/**
106+
* Returns the actual document field value
107+
*
108+
* E.g. document.someVal -> document
109+
* user.emails -> user
110+
* username -> username
111+
*
112+
* @param string $field
113+
* @return string
114+
*/
115+
protected function getFieldNameFromPropertyPath($field)
116+
{
117+
$pieces = explode('.', $field);
118+
return $pieces[0];
119+
}
120+
121+
protected function getFieldValueRecursively($fieldName, $value)
122+
{
123+
$pieces = explode('.', $fieldName);
124+
unset($pieces[0]);
125+
foreach ($pieces as $piece) {
126+
$value = $value[$piece];
127+
}
128+
return $value;
129+
}
130+
131+
}

0 commit comments

Comments
 (0)