Skip to content
This repository
Browse code

[DoctrineMongoDBBundle] added unique constraint, validator and test, …

…registered validator in DIC
  • Loading branch information...
commit 1cbd0caa8943ee7bfe8f7ba286f173ac94651287 1 parent 84fa4b5
Bulat Shakirzyanov authored January 19, 2011 fabpot committed January 19, 2011
8  src/Symfony/Bundle/DoctrineMongoDBBundle/Resources/config/mongodb.xml
@@ -51,6 +51,9 @@
51 51
 
52 52
     <!-- security/user -->
53 53
     <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>
54 57
   </parameters>
55 58
 
56 59
   <services>
@@ -86,6 +89,11 @@
86 89
     </service>
87 90
 
88 91
     <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>
89 97
 
90 98
   </services>
91 99
 </container>
9  src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Fixtures/Validator/Document.php
... ...
@@ -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
+}
143  src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/DoctrineMongoDBUniqueValidatorTest.php
... ...
@@ -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
+}
45  src/Symfony/Bundle/DoctrineMongoDBBundle/Validator/Constraints/DoctrineMongoDBUnique.php
... ...
@@ -0,0 +1,45 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the Symfony package.
  5
+ *
  6
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  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 <bulat@theopenskyproject.com>
  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
+}
131  src/Symfony/Bundle/DoctrineMongoDBBundle/Validator/Constraints/DoctrineMongoDBUniqueValidator.php
... ...
@@ -0,0 +1,131 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the Symfony package.
  5
+ *
  6
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  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 <bulat@theopenskyproject.com>
  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 notes on commit 1cbd0ca

Please sign in to comment.
Something went wrong with that request. Please try again.