diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php index 5dfbc2580..2540184cd 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php @@ -61,4 +61,9 @@ class Document * @var boolean */ public $referenceable; + + /** + * @var boolean + */ + public $uniqueNodeType; } diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php index 9097fd04f..7e34bb29f 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php @@ -306,6 +306,13 @@ class ClassMetadata implements ClassMetadataInterface */ public $referenceable = false; + /** + * READ-ONLY: If true, consider this document's node type to be unique among all mappings. + * + * @var bool + */ + public $uniqueNodeType = false; + /** * READ-ONLY: Strategy key to find field translations. * This is the key used for DocumentManagerInterface::getTranslationStrategy @@ -611,6 +618,24 @@ public function setReferenceable($referenceable) $this->referenceable = $referenceable; } + /** + * @param bool $uniqueNodeType + */ + public function setUniqueNodeType($uniqueNodeType) + { + $this->uniqueNodeType = $uniqueNodeType; + } + + /** + * Return true if this document has a unique node type among all mappings. + * + * @return bool + */ + public function hasUniqueNodeType() + { + return $this->uniqueNodeType; + } + /** * @param string $nodeType */ @@ -1481,6 +1506,10 @@ public function __sleep() $serialized[] = 'referenceable'; } + if ($this->uniqueNodeType) { + $serialized[] = 'uniqueNodeType'; + } + if ($this->lifecycleCallbacks) { $serialized[] = 'lifecycleCallbacks'; } diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php index 42eafcc56..14635cb39 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php @@ -87,6 +87,10 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $metadata->setVersioned($documentAnnot->versionable); } + if (null !== $documentAnnot->uniqueNodeType) { + $metadata->setUniqueNodeType($documentAnnot->uniqueNodeType); + } + if (null !== $documentAnnot->mixins) { $metadata->setMixins($documentAnnot->mixins); } diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php index 765336672..2e7d98950 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php @@ -79,6 +79,10 @@ public function loadMetadataForClass($className, ClassMetadata $class) $class->setReferenceable((bool) $xmlRoot['referenceable']); } + if (isset($xmlRoot['uniqueNodeType']) && $xmlRoot['uniqueNodeType'] !== 'false') { + $class->setUniqueNodeType((bool) $xmlRoot['uniqueNodeType']); + } + if (isset($xmlRoot->mixins)) { $mixins = array(); foreach ($xmlRoot->mixins->mixin as $mixin) { diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php index 7626b6e37..c55c34eaf 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php @@ -81,6 +81,10 @@ public function loadMetadataForClass($className, ClassMetadata $class) $class->setReferenceable($element['referenceable']); } + if (isset($element['uniqueNodeType']) && $element['uniqueNodeType']) { + $class->setUniqueNodeType($element['uniqueNodeType']); + } + if (isset($element['mixins'])) { $mixins = array(); foreach ($element['mixins'] as $mixin) { diff --git a/lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php b/lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php index c8db4c94c..1cb94024e 100644 --- a/lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php +++ b/lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php @@ -269,13 +269,6 @@ protected function walkSourceDocument(SourceDocument $node) $alias = $node->getAlias(); $documentFqn = $node->getDocumentFqn(); - // make sure we add the phpcr:{class,classparents} constraints - // From is dispatched first, so these will always be the primary - // constraints. - if (!$node->getStronglyTyped()) { - $this->sourceDocumentNodes[$alias] = $node; - } - // cache the metadata for this document /** @var $meta ClassMetadata */ $meta = $this->mdf->getMetadataFor($documentFqn); @@ -292,6 +285,13 @@ protected function walkSourceDocument(SourceDocument $node) } $nodeType = $meta->getNodeType(); + // make sure we add the phpcr:{class,classparents} constraints + // unless the document has a unique type; From is dispatched first, + // so these will always be the primary constraints. + if (!$meta->hasUniqueNodeType()) { + $this->sourceDocumentNodes[$alias] = $node; + } + // get the PHPCR Alias $alias = $this->qomf->selector( $alias, diff --git a/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceDocument.php b/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceDocument.php index 7795245b0..f6b72d20a 100644 --- a/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceDocument.php +++ b/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceDocument.php @@ -8,9 +8,8 @@ class SourceDocument extends AbstractLeafNode { protected $documentFqn; protected $alias; - protected $stronglyTyped; - public function __construct(AbstractNode $parent, $documentFqn, $alias, $stronglyTyped = false) + public function __construct(AbstractNode $parent, $documentFqn, $alias) { if (!is_string($alias) || empty($alias)) { throw new InvalidArgumentException(sprintf( @@ -21,7 +20,6 @@ public function __construct(AbstractNode $parent, $documentFqn, $alias, $strongl $this->documentFqn = $documentFqn; $this->alias = $alias; - $this->stronglyTyped = $stronglyTyped; parent::__construct($parent); } @@ -35,11 +33,6 @@ public function getAlias() return $this->alias; } - public function getStronglyTyped() - { - return $this->stronglyTyped; - } - public function getNodeType() { return self::NT_SOURCE; diff --git a/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceFactory.php b/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceFactory.php index 3c2963e69..2224806a7 100644 --- a/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceFactory.php +++ b/lib/Doctrine/ODM/PHPCR/Query/Builder/SourceFactory.php @@ -40,9 +40,9 @@ public function getCardinalityMap() * @factoryMethod SourceDocument * @return SourceDocument */ - public function document($documentFqn, $alias, $stronglyTyped = false) + public function document($documentFqn, $alias) { - return $this->addChild(new SourceDocument($this, $documentFqn, $alias, $stronglyTyped)); + return $this->addChild(new SourceDocument($this, $documentFqn, $alias)); } /** diff --git a/tests/Doctrine/Tests/Models/CMS/CmsProfile.php b/tests/Doctrine/Tests/Models/CMS/CmsProfile.php index df3abe1c1..2862206df 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsProfile.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsProfile.php @@ -10,7 +10,8 @@ * @PHPCRODM\Document( * nodeType="phpcr:cms_profile", * referenceable=true, - * repositoryClass="Doctrine\Tests\Models\CMS\CmsProfileRepository" + * repositoryClass="Doctrine\Tests\Models\CMS\CmsProfileRepository", + * uniqueNodeType=true * ) */ class CmsProfile diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php index 9027cdd6c..0925e5892 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php @@ -209,21 +209,22 @@ public function testEquiJoinInner($joinType, $leftClass, $rightClass, $criteria } /** - * Verify that using an outer join on a document that is strongly typed + * Verify that using an outer join on a document that is uniquely typed * results in the full expected result set. */ - public function testStronglyTypedOuterJoin() + public function testUniqueNodeTypeOuterJoin() { - $qb = $this->dm->createQueryBuilder(); + $qb = $this->dm->createQueryBuilder() ->fromDocument('Doctrine\Tests\Models\CMS\CmsUser', 'u') ->addJoinLeftOuter() - ->right()->document('Doctrine\Tests\Models\CMS\CmsProfile', 'p', true)->end() + ->right()->document('Doctrine\Tests\Models\CMS\CmsProfile', 'p')->end() ->condition()->equi('u.profiles', 'p.uuid') + ->end()->end() ->where()->orX() ->eq()->field('u.username')->literal('winstonsmith')->end() ->eq()->field('p.data')->literal('testdata')->end() ->end()->end() - ->orderBy()->asc()->field('u.username'); + ->orderBy()->asc()->field('u.username')->end()->end(); $q = $qb->getQuery(); $phpcrQuery = $q->getPhpcrQuery(); diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php index 9b6f23d5a..f9c1581a6 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php @@ -453,6 +453,22 @@ public function testReferenceableMapping($class) $this->assertTrue($class->referenceable); } + public function testLoadUniqueNodeTypeMapping() + { + $className = 'Doctrine\Tests\ODM\PHPCR\Mapping\Model\UniqueNodeTypeMappingObject'; + + return $this->loadMetadataForClassname($className); + } + + /** + * @depends testLoadUniqueNodeTypeMapping + * @param ClassMetadata $class + */ + public function testUniqueNodeTypeMapping($class) + { + $this->assertTrue($class->uniqueNodeType); + } + public function testLoadNodeTypeMapping() { $className = 'Doctrine\Tests\ODM\PHPCR\Mapping\Model\NodeTypeMappingObject'; diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php index 02a2efe6f..64a4b44a5 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php @@ -224,10 +224,11 @@ public function testNewInstance(ClassMetadata $cm) */ public function testSerialize(ClassMetadata $cm) { - $expected = 'O:40:"Doctrine\ODM\PHPCR\Mapping\ClassMetadata":19:{s:8:"nodeType";s:15:"nt:unstructured";s:10:"identifier";s:2:"id";s:4:"name";s:39:"Doctrine\Tests\ODM\PHPCR\Mapping\Person";s:11:"idGenerator";i:2;s:8:"mappings";a:5:{s:2:"id";a:7:{s:9:"fieldName";s:2:"id";s:2:"id";b:1;s:8:"strategy";s:8:"assigned";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;s:8:"property";s:2:"id";}s:8:"username";a:5:{s:9:"fieldName";s:8:"username";s:8:"property";s:8:"username";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:7:"created";a:5:{s:9:"fieldName";s:7:"created";s:8:"property";s:7:"created";s:4:"type";s:8:"datetime";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:6:"locale";a:3:{s:9:"fieldName";s:6:"locale";s:4:"type";s:6:"locale";s:8:"property";s:6:"locale";}s:15:"translatedField";a:7:{s:9:"fieldName";s:15:"translatedField";s:10:"translated";b:1;s:8:"property";s:15:"translatedField";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:5:"assoc";N;s:8:"nullable";b:0;}}s:13:"fieldMappings";a:4:{i:0;s:2:"id";i:1;s:8:"username";i:2;s:7:"created";i:3;s:15:"translatedField";}s:17:"referenceMappings";a:0:{}s:17:"referrersMappings";a:0:{}s:22:"mixedReferrersMappings";a:0:{}s:16:"childrenMappings";a:0:{}s:13:"childMappings";a:0:{}s:25:"customRepositoryClassName";s:51:"Doctrine\Tests\ODM\PHPCR\Mapping\DocumentRepository";s:18:"isMappedSuperclass";b:1;s:11:"versionable";b:1;s:18:"lifecycleCallbacks";a:1:{s:8:"postLoad";a:1:{i:0;s:8:"callback";}}s:13:"inheritMixins";b:1;s:13:"localeMapping";s:6:"locale";s:10:"translator";s:9:"attribute";s:18:"translatableFields";a:1:{i:0;s:15:"translatedField";}}'; + $expected = 'O:40:"Doctrine\ODM\PHPCR\Mapping\ClassMetadata":20:{s:8:"nodeType";s:15:"nt:unstructured";s:10:"identifier";s:2:"id";s:4:"name";s:39:"Doctrine\Tests\ODM\PHPCR\Mapping\Person";s:11:"idGenerator";i:2;s:8:"mappings";a:5:{s:2:"id";a:7:{s:9:"fieldName";s:2:"id";s:2:"id";b:1;s:8:"strategy";s:8:"assigned";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;s:8:"property";s:2:"id";}s:8:"username";a:5:{s:9:"fieldName";s:8:"username";s:8:"property";s:8:"username";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:7:"created";a:5:{s:9:"fieldName";s:7:"created";s:8:"property";s:7:"created";s:4:"type";s:8:"datetime";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:6:"locale";a:3:{s:9:"fieldName";s:6:"locale";s:4:"type";s:6:"locale";s:8:"property";s:6:"locale";}s:15:"translatedField";a:7:{s:9:"fieldName";s:15:"translatedField";s:10:"translated";b:1;s:8:"property";s:15:"translatedField";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:5:"assoc";N;s:8:"nullable";b:0;}}s:13:"fieldMappings";a:4:{i:0;s:2:"id";i:1;s:8:"username";i:2;s:7:"created";i:3;s:15:"translatedField";}s:17:"referenceMappings";a:0:{}s:17:"referrersMappings";a:0:{}s:22:"mixedReferrersMappings";a:0:{}s:16:"childrenMappings";a:0:{}s:13:"childMappings";a:0:{}s:25:"customRepositoryClassName";s:51:"Doctrine\Tests\ODM\PHPCR\Mapping\DocumentRepository";s:18:"isMappedSuperclass";b:1;s:11:"versionable";b:1;s:14:"uniqueNodeType";b:1;s:18:"lifecycleCallbacks";a:1:{s:8:"postLoad";a:1:{i:0;s:8:"callback";}}s:13:"inheritMixins";b:1;s:13:"localeMapping";s:6:"locale";s:10:"translator";s:9:"attribute";s:18:"translatableFields";a:1:{i:0;s:15:"translatedField";}}'; $cm->setCustomRepositoryClassName('DocumentRepository'); $cm->setVersioned(true); + $cm->setUniqueNodeType(true); $cm->addLifecycleCallback('callback', 'postLoad'); $cm->isMappedSuperclass = true; $this->assertEquals($expected, serialize($cm)); @@ -235,13 +236,14 @@ public function testSerialize(ClassMetadata $cm) public function testUnserialize() { - $cm = unserialize('O:40:"Doctrine\ODM\PHPCR\Mapping\ClassMetadata":15:{s:8:"nodeType";s:15:"nt:unstructured";s:10:"identifier";s:2:"id";s:4:"name";s:39:"Doctrine\Tests\ODM\PHPCR\Mapping\Person";s:11:"idGenerator";i:1;s:8:"mappings";a:5:{s:2:"id";a:7:{s:9:"fieldName";s:2:"id";s:2:"id";b:1;s:8:"strategy";s:10:"repository";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;s:8:"property";s:2:"id";}s:8:"username";a:5:{s:9:"fieldName";s:8:"username";s:8:"property";s:8:"username";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:7:"created";a:5:{s:9:"fieldName";s:7:"created";s:8:"property";s:7:"created";s:4:"type";s:8:"datetime";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:6:"locale";a:3:{s:9:"fieldName";s:6:"locale";s:4:"type";s:6:"locale";s:8:"property";s:6:"locale";}s:15:"translatedField";a:7:{s:9:"fieldName";s:15:"translatedField";s:4:"type";s:6:"string";s:10:"translated";b:1;s:8:"property";s:15:"translatedField";s:10:"multivalue";b:0;s:5:"assoc";N;s:8:"nullable";b:0;}}s:13:"fieldMappings";a:4:{i:0;s:2:"id";i:1;s:8:"username";i:2;s:7:"created";i:3;s:15:"translatedField";}s:17:"referenceMappings";a:0:{}s:17:"referrersMappings";a:0:{}s:22:"mixedReferrersMappings";a:0:{}s:16:"childrenMappings";a:0:{}s:13:"childMappings";a:0:{}s:25:"customRepositoryClassName";s:51:"Doctrine\Tests\ODM\PHPCR\Mapping\DocumentRepository";s:18:"isMappedSuperclass";b:1;s:11:"versionable";b:1;s:18:"lifecycleCallbacks";a:1:{s:8:"postLoad";a:1:{i:0;s:8:"callback";}}}'); + $cm = unserialize('O:40:"Doctrine\ODM\PHPCR\Mapping\ClassMetadata":16:{s:8:"nodeType";s:15:"nt:unstructured";s:10:"identifier";s:2:"id";s:4:"name";s:39:"Doctrine\Tests\ODM\PHPCR\Mapping\Person";s:11:"idGenerator";i:1;s:8:"mappings";a:5:{s:2:"id";a:7:{s:9:"fieldName";s:2:"id";s:2:"id";b:1;s:8:"strategy";s:10:"repository";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;s:8:"property";s:2:"id";}s:8:"username";a:5:{s:9:"fieldName";s:8:"username";s:8:"property";s:8:"username";s:4:"type";s:6:"string";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:7:"created";a:5:{s:9:"fieldName";s:7:"created";s:8:"property";s:7:"created";s:4:"type";s:8:"datetime";s:10:"multivalue";b:0;s:8:"nullable";b:0;}s:6:"locale";a:3:{s:9:"fieldName";s:6:"locale";s:4:"type";s:6:"locale";s:8:"property";s:6:"locale";}s:15:"translatedField";a:7:{s:9:"fieldName";s:15:"translatedField";s:4:"type";s:6:"string";s:10:"translated";b:1;s:8:"property";s:15:"translatedField";s:10:"multivalue";b:0;s:5:"assoc";N;s:8:"nullable";b:0;}}s:13:"fieldMappings";a:4:{i:0;s:2:"id";i:1;s:8:"username";i:2;s:7:"created";i:3;s:15:"translatedField";}s:17:"referenceMappings";a:0:{}s:17:"referrersMappings";a:0:{}s:22:"mixedReferrersMappings";a:0:{}s:16:"childrenMappings";a:0:{}s:13:"childMappings";a:0:{}s:25:"customRepositoryClassName";s:51:"Doctrine\Tests\ODM\PHPCR\Mapping\DocumentRepository";s:18:"isMappedSuperclass";b:1;s:11:"versionable";b:1;s:14:"uniqueNodeType";b:1;s:18:"lifecycleCallbacks";a:1:{s:8:"postLoad";a:1:{i:0;s:8:"callback";}}}'); $this->assertInstanceOf('Doctrine\ODM\PHPCR\Mapping\ClassMetadata', $cm); $this->assertEquals(array('callback'), $cm->getLifecycleCallbacks('postLoad')); $this->assertTrue($cm->isMappedSuperclass); $this->assertTrue($cm->versionable); + $this->assertTrue($cm->uniqueNodeType); $this->assertTrue($cm->inheritMixins); $this->assertEquals('Doctrine\Tests\ODM\PHPCR\Mapping\DocumentRepository', $cm->customRepositoryClassName); } diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/UniqueNodeTypeMappingObject.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/UniqueNodeTypeMappingObject.php new file mode 100644 index 000000000..6a9cebb31 --- /dev/null +++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/UniqueNodeTypeMappingObject.php @@ -0,0 +1,16 @@ + + + + + + diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.UniqueNodeTypeMappingObject.dcm.yml b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.UniqueNodeTypeMappingObject.dcm.yml new file mode 100644 index 000000000..b2caf0b5a --- /dev/null +++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.UniqueNodeTypeMappingObject.dcm.yml @@ -0,0 +1,3 @@ +Doctrine\Tests\ODM\PHPCR\Mapping\Model\UniqueNodeTypeMappingObject: + uniqueNodeType: true + id: id