diff --git a/CHANGELOG.md b/CHANGELOG.md
index a50bd2ba3..e8f94fa2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
Changelog
=========
+dev-master
+----------
+
+* **2016-06-08** [Feature] Allow children of a document to be restricted to a
+ certain class or forbidden.
+
1.3.1
-----
diff --git a/composer.json b/composer.json
index cc1a88a46..cc0febc01 100644
--- a/composer.json
+++ b/composer.json
@@ -46,7 +46,7 @@
"bin": ["bin/phpcrodm", "bin/phpcrodm.php"],
"extra": {
"branch-alias": {
- "dev-master": "1.3-dev"
+ "dev-master": "1.4-dev"
}
}
}
diff --git a/doctrine-phpcr-odm-mapping.xsd b/doctrine-phpcr-odm-mapping.xsd
index cd09ee34a..adaff795a 100644
--- a/doctrine-phpcr-odm-mapping.xsd
+++ b/doctrine-phpcr-odm-mapping.xsd
@@ -44,6 +44,10 @@
+
+
+
+
@@ -138,6 +142,7 @@
-->
+
@@ -159,6 +164,7 @@
+
diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php
index 2540184cd..be201e070 100644
--- a/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php
+++ b/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php
@@ -66,4 +66,14 @@ class Document
* @var boolean
*/
public $uniqueNodeType;
+
+ /**
+ * @var array
+ */
+ public $childClasses = array();
+
+ /**
+ * @var boolean
+ */
+ public $isLeaf;
}
diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
index 7e34bb29f..631061631 100644
--- a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
+++ b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
@@ -31,6 +31,7 @@
use Doctrine\Common\ClassLoader;
use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\InstantiatorInterface;
+use Doctrine\ODM\PHPCR\Exception\OutOfBoundsException;
/**
* Metadata class
@@ -328,6 +329,23 @@ class ClassMetadata implements ClassMetadataInterface
*/
public $parentClasses = array();
+ /**
+ * READ-ONLY: Child class restrictions.
+ *
+ * If empty then any classes are permitted.
+ *
+ * @var array
+ */
+ public $childClasses = array();
+
+ /**
+ * READ-ONLY: If the document should be act as a leaf-node and therefore
+ * not be allowed children.
+ *
+ * @var boolean
+ */
+ public $isLeaf = false;
+
/**
* The inherited fields of this class
*
@@ -453,6 +471,57 @@ public function validateIdentifier()
}
}
+ /**
+ * Validate that childClasses is empty if isLeaf is true.
+ *
+ * @throws MappingException if there is a conflict between isLeaf and childClasses.
+ */
+ public function validateChildClasses()
+ {
+ if (count($this->childClasses) > 0 && $this->isLeaf) {
+ throw new MappingException(sprintf(
+ 'Cannot map a document as a leaf and define child classes for "%s"',
+ $this->name
+ ));
+ }
+ }
+
+ /**
+ * Assert that the given class FQN can be a child of the document this
+ * metadata represents.
+ *
+ * @param string $classFqn
+ * @throws OutOfBoundsException
+ */
+ public function assertValidChildClass(ClassMetadata $class)
+ {
+ if ($this->isLeaf()) {
+ throw new OutOfBoundsException(sprintf(
+ 'Document "%s" has been mapped as a leaf. It cannot have children',
+ $this->name
+ ));
+ }
+
+ $childClasses = $this->getChildClasses();
+
+ if (0 === count($childClasses)) {
+ return;
+ }
+
+ foreach ($childClasses as $childClass) {
+ if ($class->name === $childClass || $class->reflClass->isSubclassOf($childClass)) {
+ return;
+ }
+ }
+
+ throw new OutOfBoundsException(sprintf(
+ 'Document "%s" does not allow children of type "%s". Allowed child classes "%s"',
+ $this->name,
+ $class->name,
+ implode('", "', $childClasses)
+ ));
+ }
+
/**
* Validate whether this class needs to be referenceable.
*
@@ -1123,6 +1192,49 @@ public function getParentClasses()
return $this->parentClasses;
}
+ /**
+ * Return the class names or interfaces that children of this document must
+ * be an instance of.
+ *
+ * @return string[]
+ */
+ public function getChildClasses()
+ {
+ return $this->childClasses;
+ }
+
+ /**
+ * Set the class names or interfaces that children of this document must be
+ * instance of.
+ *
+ * @param string[] $childClasses
+ */
+ public function setChildClasses(array $childClasses)
+ {
+ $this->childClasses = $childClasses;
+ }
+
+ /**
+ * Return true if this is designated as a leaf node.
+ *
+ * @return bool
+ */
+ public function isLeaf()
+ {
+ return $this->isLeaf;
+ }
+
+ /**
+ * Set if this document should act as a leaf node.
+ *
+ * @param bool $isLeaf
+ */
+ public function setIsLeaf($isLeaf)
+ {
+ $this->isLeaf = $isLeaf;
+ }
+
+
/**
* Checks whether the class will generate an id via the repository.
*
diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadataFactory.php
index 5928341dd..1fd1c62ae 100644
--- a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadataFactory.php
+++ b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadataFactory.php
@@ -256,6 +256,7 @@ protected function validateRuntimeMetadata($class, $parent)
$class->validateIdentifier();
$class->validateReferenceable();
$class->validateReferences();
+ $class->validateChildClasses();
$class->validateLifecycleCallbacks($this->getReflectionService());
$class->validateTranslatables();
diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php
index 14635cb39..67264a94b 100644
--- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php
+++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php
@@ -111,6 +111,12 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$metadata->setTranslator($documentAnnot->translator);
}
+ if (array() !== $documentAnnot->childClasses) {
+ $metadata->setChildClasses($documentAnnot->childClasses);
+ }
+
+ $metadata->setIsLeaf($documentAnnot->isLeaf);
+
foreach ($reflClass->getProperties() as $property) {
if ($metadata->isInheritedField($property->name)
&& $metadata->name !== $property->getDeclaringClass()->getName()
diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php
index 2e7d98950..687ffdce5 100644
--- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php
+++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php
@@ -83,6 +83,18 @@ public function loadMetadataForClass($className, ClassMetadata $class)
$class->setUniqueNodeType((bool) $xmlRoot['uniqueNodeType']);
}
+ if (isset($xmlRoot['is-leaf'])) {
+ if (!in_array($value = $xmlRoot['is-leaf'], array('true', 'false'))) {
+ throw new MappingException(sprintf(
+ 'Value of is-leaf must be "true" or "false", got "%s" for class "%s"',
+ $value, $className
+ ));
+ }
+
+ $class->setIsLeaf($value == 'true' ? true : false);
+ }
+
+
if (isset($xmlRoot->mixins)) {
$mixins = array();
foreach ($xmlRoot->mixins->mixin as $mixin) {
@@ -259,6 +271,14 @@ public function loadMetadataForClass($className, ClassMetadata $class)
$class->mapField($mapping);
}
+ if (isset($xmlRoot->{'child-class'})) {
+ $childClasses = array();
+ foreach ($xmlRoot->{'child-class'} as $requiredClass) {
+ $childClasses[] = $requiredClass['name'];
+ }
+ $class->setChildClasses($childClasses);
+ }
+
$class->validateClassMapping();
}
diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php
index c55c34eaf..ac493f96b 100644
--- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php
+++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php
@@ -255,6 +255,14 @@ public function loadMetadataForClass($className, ClassMetadata $class)
}
}
+ if (isset($element['child_classes'])) {
+ $class->setChildClasses($element['child_classes']);
+ }
+
+ if (isset($element['is_leaf'])) {
+ $class->setIsLeaf($element['is_leaf']);
+ }
+
$class->validateClassMapping();
}
diff --git a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php
index 880b63428..4a0f9691c 100644
--- a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php
+++ b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -54,6 +54,7 @@
use PHPCR\Util\PathHelper;
use PHPCR\Util\NodeHelper;
use Jackalope\Session as JackalopeSession;
+use Doctrine\ODM\PHPCR\Exception\OutOfBoundsException;
/**
* Unit of work class
@@ -2332,6 +2333,8 @@ private function executeInserts($documents)
}
$parentNode = $this->session->getNode(PathHelper::getParentPath($id));
+ $this->validateChildClass($parentNode, $class);
+
$nodename = PathHelper::getNodeName($id);
$node = $parentNode->addNode($nodename, $class->nodeType);
if ($class->node) {
@@ -2731,6 +2734,9 @@ private function executeMoves($documents)
);
}
+ $parentNode = $this->session->getNode(PathHelper::getParentPath($targetPath));
+ $this->validateChildClass($parentNode, $class);
+
$this->session->move($sourcePath, $targetPath);
// update fields nodename, parentMapping and depth if they exist in this type
@@ -3968,4 +3974,23 @@ public function invokeGlobalEvent($eventName, EventArgs $event)
$this->eventManager->dispatchEvent($eventName, $event);
}
}
+
+ /**
+ * If the parent node has child restrictions, ensure that the given
+ * class name is within them.
+ *
+ * @param NodeInterface $parentNode
+ * @param string $classFqn
+ */
+ private function validateChildClass(NodeInterface $parentNode, ClassMetadata $class)
+ {
+ $parentClass = $this->documentClassMapper->getClassName($this->dm, $parentNode);
+
+ if (null === $parentClass) {
+ return;
+ }
+
+ $metadata = $this->dm->getClassMetadata($parentClass);
+ $metadata->assertValidChildClass($class);
+ }
}
diff --git a/tests/Doctrine/Tests/Models/CMS/CmsArticleFolder.php b/tests/Doctrine/Tests/Models/CMS/CmsArticleFolder.php
new file mode 100644
index 000000000..0cee22bef
--- /dev/null
+++ b/tests/Doctrine/Tests/Models/CMS/CmsArticleFolder.php
@@ -0,0 +1,22 @@
+assertSame($child->parent, $parent);
$this->assertSame('parent', $parent->nodename);
}
+
+ /**
+ * @expectedException Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
+ * @expectedExceptionMessage Document "Doctrine\Tests\Models\CMS\CmsArticleFolder" does not allow children of type "Doctrine\Tests\Models\CMS\CmsGroup". Allowed child classes "Doctrine\Tests\Models\CMS\CmsArticle"
+ */
+ public function testRequiredClassesInvalidChildren()
+ {
+ $articleFolder = new CmsArticleFolder();
+ $articleFolder->id = '/functional/articles';
+
+ $article = new CmsGroup();
+ $article->id = '/functional/articles/address';
+ $article->name = 'invalid-child';
+
+ $this->dm->persist($articleFolder);
+ $this->dm->persist($article);
+ $this->dm->flush();
+ }
+
+ public function testRequiredClassesValidChildren()
+ {
+ $articleFolder = new CmsArticleFolder();
+ $articleFolder->id = '/functional/articles';
+
+ $article = new CmsArticle();
+ $article->id = '/functional/articles/article';
+ $article->topic = 'greetings';
+ $article->text = 'Hello World';
+
+ $this->dm->persist($articleFolder);
+ $this->dm->persist($article);
+ $this->dm->flush();
+ }
+
+ /**
+ * @expectedException Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
+ * @expectedExceptionMessage Document "Doctrine\Tests\Models\CMS\CmsArticleFolder" does not allow children of type "Doctrine\Tests\Models\CMS\CmsGroup". Allowed child classes "Doctrine\Tests\Models\CMS\CmsArticle"
+ */
+ public function testRequiredClassesInvalidUpdate()
+ {
+ $articleFolder = new CmsArticleFolder();
+ $articleFolder->id = '/functional/articles';
+
+ $article = new CmsGroup();
+ $article->id = '/functional/address';
+ $article->name = 'invalid-child';
+
+ $this->dm->persist($articleFolder);
+ $this->dm->persist($article);
+ $this->dm->flush();
+
+ $this->dm->move($article, '/functional/articles/address');
+ $this->dm->flush();
+ }
+
+ public function testRequiredClassesAddToChildrenValid()
+ {
+ $post = new CmsBlogPost();
+ $post->name = 'hello';
+
+ $postFolder = new CmsBlogFolder();
+ $postFolder->id = '/functional/posts';
+ $postFolder->posts = new ArrayCollection(array(
+ $post
+ ));
+
+ $this->dm->persist($postFolder);
+ $this->dm->flush();
+ }
+
+ /**
+ * @expectedException Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
+ * @expectedExceptionMessage Document "Doctrine\Tests\Models\CMS\CmsBlogFolder" does not allow children of type "Doctrine\Tests\Models\CMS\CmsBlogInvalidChild". Allowed child classes "Doctrine\Tests\Models\CMS\CmsBlogPost"
+ */
+ public function testRequiredClassesAddToChildrenInvalid()
+ {
+ $post = new CmsBlogInvalidChild();
+ $post->name = 'hello';
+
+ $postFolder = new CmsBlogFolder();
+ $postFolder->id = '/functional/posts';
+ $postFolder->posts = new ArrayCollection(array(
+ $post
+ ));
+
+ $this->dm->persist($postFolder);
+ $this->dm->flush();
+ }
+
+ /**
+ * @expectedException Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
+ * @expectedExceptionMessage Document "Doctrine\Tests\Models\CMS\CmsBlogFolder" does not allow children of type "Doctrine\Tests\Models\CMS\CmsBlogInvalidChild". Allowed child classes "Doctrine\Tests\Models\CMS\CmsBlogPost"
+ */
+ public function testRequiredClassesAddToChildrenInvalidOnUpdate()
+ {
+ $post = new CmsBlogPost();
+ $post->name = 'hello';
+
+ $postFolder = new CmsBlogFolder();
+ $postFolder->id = '/functional/posts';
+ $postFolder->posts = new ArrayCollection(array(
+ $post
+ ));
+
+ $this->dm->persist($postFolder);
+ $this->dm->flush();
+ $this->dm->clear();
+
+ $postFolder = $this->dm->find(null, '/functional/posts');
+
+ $post = new CmsBlogInvalidChild();
+ $post->name = 'wolrd';
+ $postFolder->posts->add($post);
+
+ $this->dm->persist($postFolder);
+ $this->dm->flush();
+ $this->dm->clear();
+ }
}
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php
index f3a5d6bc6..745f5daf5 100644
--- a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/AbstractMappingDriverTest.php
@@ -798,4 +798,36 @@ public function testUuidMappingNonReferenceable()
return $this->loadMetadataForClassname($className);
}
+
+ public function testLoadChildClassesMapping()
+ {
+ $className = 'Doctrine\Tests\ODM\PHPCR\Mapping\Model\ChildClassesObject';
+
+ return $this->loadMetadataForClassname($className);
+ }
+
+ /**
+ * @depends testLoadChildClassesMapping
+ * @param ClassMetadata $class
+ */
+ public function testChildClassesMapping($class)
+ {
+ $this->assertEquals(array('stdClass'), $class->getChildClasses());
+ }
+
+ public function testLoadIsLeafMapping()
+ {
+ $className = 'Doctrine\Tests\ODM\PHPCR\Mapping\Model\IsLeafObject';
+
+ return $this->loadMetadataForClassname($className);
+ }
+
+ /**
+ * @depends testLoadIsLeafMapping
+ * @param ClassMetadata $class
+ */
+ public function testIsLeafMapping($class)
+ {
+ $this->assertTrue($class->isLeaf());
+ }
}
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataFactoryTest.php
index 813ad3666..a0aee72cd 100644
--- a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataFactoryTest.php
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataFactoryTest.php
@@ -144,6 +144,15 @@ public function testValidateTranslatableNoStrategy()
$this->getMetadataFor('Doctrine\Tests\ODM\PHPCR\Mapping\Model\TranslatorMappingObjectNoStrategy');
}
+ /**
+ * @expectedException \Doctrine\ODM\PHPCR\Mapping\MappingException
+ * @expectedExceptionMessage Cannot map a document as a leaf and define child classes for "Doctrine\Tests\ODM\PHPCR\Mapping\Model\ChildClassesAndLeafObject"
+ */
+ public function testValidateChildClassesIfLeafConflict()
+ {
+ $this->getMetadataFor('Doctrine\Tests\ODM\PHPCR\Mapping\Model\ChildClassesAndLeafObject');
+ }
+
public function testValidateTranslatable()
{
$this->getMetadataFor('Doctrine\Tests\ODM\PHPCR\Mapping\Model\TranslatorMappingObject');
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php
index 64a4b44a5..f64170789 100644
--- a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php
@@ -42,8 +42,8 @@ public function testIsValidNodename(ClassMetadata $cm)
$this->assertInstanceOf('PHPCR\RepositoryException', $cm->isValidNodename(':'));
$this->assertInstanceOf('PHPCR\RepositoryException', $cm->isValidNodename('x/y'));
- $this->assertNull($cm->isValidNodename('a:b'));
- $this->assertNull($cm->isValidNodename('b'));
+ $cm->isValidNodename('a:b');
+ $cm->isValidNodename('b');
}
/**
@@ -324,6 +324,88 @@ public function testClassMetadataInstanceSerializationTranslationProperties($cm)
$this->assertTrue(in_array('translatedField', $cm->translatableFields));
$this->assertEquals('locale', $cm->localeMapping);
}
+
+ /**
+ * It should throw an exception if given a child class FQN when the
+ * metadata is for a leaf.
+ *
+ * @expectedException \Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
+ * @expectedExceptionMessage has been mapped as a leaf
+ */
+ public function testAssertValidChildClassesIsLeaf()
+ {
+ $cm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Person');
+ $childCm = new ClassMetadata('stdClass');
+ $cm->setIsLeaf(true);
+ $cm->assertValidChildClass($childCm);
+ }
+
+ /**
+ * It should return early if the mapped child classes value is an empty array (i.e. any child classes are permitted).
+ */
+ public function testAssertValidChildClassesEmpty()
+ {
+ $cm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Person');
+ $childCm = new ClassMetadata('stdClass');
+ $cm->setChildClasses(array());
+ $cm->assertValidChildClass($childCm);
+ }
+
+ /**
+ * It should return early if the given class is an allowed child class.
+ */
+ public function testAssertValidChildClassesAllowed()
+ {
+ $cm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Person');
+ $cm->setChildClasses(array('stdClass'));
+ $childCm = new ClassMetadata('stdClass');
+ $childCm->initializeReflection(new RuntimeReflectionService());
+ $cm->assertValidChildClass($childCm);
+ }
+
+ /**
+ * It should return early if the given class is an instance of an allowed class.
+ */
+ public function testAssertValidChildClassInstance()
+ {
+ $cm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Person');
+ $cm->initializeReflection(new RuntimeReflectionService());
+ $childCm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Customer');
+ $childCm->initializeReflection(new RuntimeReflectionService());
+ $cm->setChildClasses(array('Doctrine\Tests\ODM\PHPCR\Mapping\Person'));
+ $result = $cm->assertValidChildClass($childCm);
+ $this->assertNull($result);
+ }
+
+ /**
+ * It should return early if the given class implements an allowed interface.
+ */
+ public function testAssertValidChildClassInterface()
+ {
+ $cm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Person');
+ $cm->initializeReflection(new RuntimeReflectionService());
+ $childCm = new ClassMetadata('ArrayAccess');
+ $childCm->initializeReflection(new RuntimeReflectionService());
+ $cm->setChildClasses(array('ArrayAccess'));
+ $result = $cm->assertValidChildClass($childCm);
+ $this->assertNull($result);
+ }
+
+ /**
+ * It should throw an exception if the given class is not allowed.
+ *
+ * @expectedException \Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
+ * @expectedExceptionMessage does not allow children of type "stdClass"
+ */
+ public function testAssertValidChildClassesNotAllowed()
+ {
+ $cm = new ClassMetadata('Doctrine\Tests\ODM\PHPCR\Mapping\Person');
+ $cm->initializeReflection(new RuntimeReflectionService());
+ $childCm = new ClassMetadata('stdClass');
+ $childCm->initializeReflection(new RuntimeReflectionService());
+ $cm->setChildClasses(array('Doctrine\Tests\ODM\PHPCR\Mapping\Person'));
+ $cm->assertValidChildClass($childCm);
+ }
}
class Customer extends Person
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/ChildClassesAndLeafObject.php b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/ChildClassesAndLeafObject.php
new file mode 100644
index 000000000..5748a1e89
--- /dev/null
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/ChildClassesAndLeafObject.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/xml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.IsLeafObject.dcm.xml b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/xml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.IsLeafObject.dcm.xml
new file mode 100644
index 000000000..142cc7cfc
--- /dev/null
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/xml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.IsLeafObject.dcm.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.ChildClassesObject.dcm.yml b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.ChildClassesObject.dcm.yml
new file mode 100644
index 000000000..fce4a1275
--- /dev/null
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.ChildClassesObject.dcm.yml
@@ -0,0 +1,5 @@
+Doctrine\Tests\ODM\PHPCR\Mapping\Model\ChildClassesObject:
+ id: id
+ child_classes: [ "stdClass" ]
+ is_leaf: false
+
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.IsLeafObject.dcm.yml b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.IsLeafObject.dcm.yml
new file mode 100644
index 000000000..1a05f773c
--- /dev/null
+++ b/tests/Doctrine/Tests/ODM/PHPCR/Mapping/Model/yml/Doctrine.Tests.ODM.PHPCR.Mapping.Model.IsLeafObject.dcm.yml
@@ -0,0 +1,4 @@
+Doctrine\Tests\ODM\PHPCR\Mapping\Model\IsLeafObject:
+ id: id
+ is_leaf: true
+
diff --git a/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php b/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php
index ee64b2b84..f8b5f2eed 100644
--- a/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php
+++ b/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php
@@ -16,17 +16,34 @@
*/
class UnitOfWorkTest extends PHPCRTestCase
{
- /** @var DocumentManager */
+ /**
+ * @var DocumentManager
+ */
private $dm;
- /** @var UnitOfWork */
+
+ /**
+ * @var UnitOfWork
+ */
private $uow;
- /** @var Factory */
+
+ /**
+ * @var Factory
+ */
private $factory;
- /** @var \PHPCR\SessionInterface */
+
+ /**
+ * @var \PHPCR\SessionInterface
+ */
private $session;
- /** @var \Jackalope\ObjectManager */
+
+ /**
+ * @var \Jackalope\ObjectManager
+ */
private $objectManager;
- /** @var string */
+
+ /**
+ * @var string
+ */
private $type;
public function setUp()