Selaa lähdekoodia

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

Bulat Shakirzyanov 14 vuotta sitten
vanhempi
commit
1cbd0caa89

+ 8 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Resources/config/mongodb.xml

@@ -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>

+ 9 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Fixtures/Validator/Document.php

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

+ 143 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/DoctrineMongoDBUniqueValidatorTest.php

@@ -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;
+    }
+}

+ 45 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Validator/Constraints/DoctrineMongoDBUnique.php

@@ -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;
+    }
+}

+ 131 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Validator/Constraints/DoctrineMongoDBUniqueValidator.php

@@ -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;
+    }
+
+}