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 a4f3c7281..1cb94024e 100644 --- a/lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php +++ b/lib/Doctrine/ODM/PHPCR/Query/Builder/ConverterPhpcr.php @@ -197,6 +197,13 @@ public function getQuery(QueryBuilder $builder) ); } + // be explicit in what we select + if (empty($this->columns)) { + foreach (array_keys($this->sourceDocumentNodes) as $selectorName) { + $this->columns[] = $this->qomf->column($selectorName); + } + } + // for each document source add phpcr:{class,classparents} restrictions foreach ($this->sourceDocumentNodes as $sourceNode) { $documentFqn = $this->aliasMetadata[$sourceNode->getAlias()]->getName(); @@ -262,11 +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. - $this->sourceDocumentNodes[$alias] = $node; - // cache the metadata for this document /** @var $meta ClassMetadata */ $meta = $this->mdf->getMetadataFor($documentFqn); @@ -283,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/tests/Doctrine/Tests/Models/CMS/CmsProfile.php b/tests/Doctrine/Tests/Models/CMS/CmsProfile.php new file mode 100644 index 000000000..2862206df --- /dev/null +++ b/tests/Doctrine/Tests/Models/CMS/CmsProfile.php @@ -0,0 +1,74 @@ +id; + } + + public function getUuid() + { + return $this->uuid; + } + + public function setData($data) + { + $this->data = $data; + } + + public function getData() + { + return $this->data; + } + + public function setUser(CmsUser $user) + { + $this->user = $user; + } + + public function getUser() + { + return $this->user; + } +} + +class CmsProfileRepository extends DocumentRepository implements RepositoryIdInterface +{ + /** + * Generate a document id + * + * @param object $document + * @return string + */ + public function generateId($document, $parent = null) + { + return '/functional/' . $document->user->username . '/' . $document->data; + } +} diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 2e697e91f..5a674a335 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -28,6 +28,8 @@ class CmsUser public $articles; /** @PHPCRODM\ReferenceMany(targetDocument="CmsGroup") */ public $groups; + /** @PHPCRODM\ReferenceMany(targetDocument="CmsProfile") */ + public $profiles; /** @PHPCRODM\Children() */ public $children; /** @PHPCRODM\Child(nodeName="assistant", cascade="persist") */ @@ -82,6 +84,17 @@ public function getGroups() { return $this->groups; } + + public function addProfile(CmsProfile $profile) + { + $this->profiles[] = $profile; + $profile->setUser($this); + } + + public function getProfiles() + { + return $this->profiles; + } } class CmsUserRepository extends DocumentRepository implements RepositoryIdInterface diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php index dee1d1828..0925e5892 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/QueryBuilderJoinTest.php @@ -8,6 +8,7 @@ use Doctrine\ODM\PHPCR\Query\Builder\AbstractNode as QBConstants; use Doctrine\Tests\ODM\PHPCR\PHPCRFunctionalTestCase; use Doctrine\Tests\Models\CMS\CmsAddress; +use Doctrine\Tests\Models\CMS\CmsProfile; use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsAuditItem; @@ -23,6 +24,9 @@ public function setUp() $this->resetFunctionalNode($this->dm); + $ntm = $this->dm->getPhpcrSession()->getWorkspace()->getNodeTypeManager(); + $ntm->registerNodeTypesCnd('[phpcr:cms_profile] > nt:unstructured', true); + $address1 = new CmsAddress; $address1->country = 'France'; $address1->city = 'Lyon'; @@ -40,6 +44,11 @@ public function setUp() $user->address = $address1; $this->dm->persist($user); + $profile = new CmsProfile(); + $profile->setData('testdata'); + $user->addProfile($profile); + $this->dm->persist($profile); + $user = new CmsUser; $user->username = 'winstonsmith'; $user->address = $address2; @@ -198,4 +207,33 @@ public function testEquiJoinInner($joinType, $leftClass, $rightClass, $criteria } } } + + /** + * Verify that using an outer join on a document that is uniquely typed + * results in the full expected result set. + */ + public function testUniqueNodeTypeOuterJoin() + { + $qb = $this->dm->createQueryBuilder() + ->fromDocument('Doctrine\Tests\Models\CMS\CmsUser', 'u') + ->addJoinLeftOuter() + ->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')->end()->end(); + + $q = $qb->getQuery(); + $phpcrQuery = $q->getPhpcrQuery(); + $phpcrRes = $phpcrQuery->execute(); + $phpcrRows = $phpcrRes->getRows(); + + $this->assertCount(2, $phpcrRows); + foreach ($phpcrRows as $key => $row) { + $this->assertEquals($key == 0 ? '/functional/dantleech' : '/functional/winstonsmith', $row->getPath('u')); + } + } } 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