From 09c55913206b477f2120e09b47989d544a289ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20J=C3=A4ger?= Date: Sat, 19 Mar 2011 21:26:05 +0100 Subject: [PATCH] Added Child annotation and File document Now you can include children in your documents by annotating them with Child. The ODM will automatically create a child node in the repository with the given name (if a document is assigned ...). A useful example is the File document that has a child of class Resource. --- README.markdown | 15 ++ lib/Doctrine/ODM/PHPCR/Document/File.php | 34 +++ lib/Doctrine/ODM/PHPCR/Document/Folder.php | 23 ++ lib/Doctrine/ODM/PHPCR/Document/Resource.php | 37 ++++ .../PHPCR/Mapping/Driver/AnnotationDriver.php | 3 + .../Mapping/Driver/DoctrineAnnotations.php | 4 + .../ODM/PHPCR/Mapping/Driver/XmlDriver.php | 9 + .../ODM/PHPCR/Mapping/Driver/YamlDriver.php | 9 + lib/Doctrine/ODM/PHPCR/UnitOfWork.php | 64 +++++- .../Tests/ODM/PHPCR/Functional/ChildTest.php | 198 ++++++++++++++++++ .../Tests/ODM/PHPCR/Functional/FileTest.php | 60 ++++++ .../ODM/PHPCR/PHPCRFunctionalTestCase.php | 7 +- 12 files changed, 452 insertions(+), 11 deletions(-) create mode 100644 lib/Doctrine/ODM/PHPCR/Document/File.php create mode 100644 lib/Doctrine/ODM/PHPCR/Document/Folder.php create mode 100644 lib/Doctrine/ODM/PHPCR/Document/Resource.php create mode 100644 tests/Doctrine/Tests/ODM/PHPCR/Functional/ChildTest.php create mode 100644 tests/Doctrine/Tests/ODM/PHPCR/Functional/FileTest.php diff --git a/README.markdown b/README.markdown index f8b8dbbe5..2682f62b7 100644 --- a/README.markdown +++ b/README.markdown @@ -22,6 +22,21 @@ Notes Getting Started --------------- + 0. Register custom node types in Jackrabbit + + - stop your Jackrabbit instance + - run "java -jar jackrabbit-standalone-2.2.4.jar --cli file://" + - enter "registernodetype " + - enter "quit" + - start your server again ... + + where phpcr.cnd contains + + + [phpcr:managed] + mixin + - phpcr:alias (STRING) + 1. Define one of those mapping drivers // Annotation driver diff --git a/lib/Doctrine/ODM/PHPCR/Document/File.php b/lib/Doctrine/ODM/PHPCR/Document/File.php new file mode 100644 index 000000000..b9fc36391 --- /dev/null +++ b/lib/Doctrine/ODM/PHPCR/Document/File.php @@ -0,0 +1,34 @@ +content === null) + { + $this->content = new Resource(); + } + $this->content->setFileData(file_get_contents($filename)); + } + +} diff --git a/lib/Doctrine/ODM/PHPCR/Document/Folder.php b/lib/Doctrine/ODM/PHPCR/Document/Folder.php new file mode 100644 index 000000000..aabb6c8cd --- /dev/null +++ b/lib/Doctrine/ODM/PHPCR/Document/Folder.php @@ -0,0 +1,23 @@ +data = $data; + } +} + diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php index 02d0328f4..7c93f092c 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php @@ -140,6 +140,9 @@ public function loadMetadataForClass($className, ClassMetadata $class) } elseif ($fieldAnnot instanceof \Doctrine\ODM\PHPCR\Mapping\Node) { $mapping = array_merge($mapping, (array) $fieldAnnot); $class->mapNode($mapping); + } elseif ($fieldAnnot instanceof \Doctrine\ODM\PHPCR\Mapping\Child) { + $mapping = array_merge($mapping, (array) $fieldAnnot); + $class->mapChild($mapping); } elseif ($fieldAnnot instanceof \Doctrine\ODM\PHPCR\Mapping\ReferenceOne) { $cascade = 0; foreach ($fieldAnnot->cascade as $cascadeMode) { diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/DoctrineAnnotations.php index d2402cd61..7c7afda69 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/DoctrineAnnotations.php @@ -82,3 +82,7 @@ final class ReferenceMany extends Reference public $cascade = array(); public $mappedBy; } +class Child extends Annotation +{ + public $name; +} diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php index 3cdfc3db4..e2554e7d9 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php @@ -89,6 +89,10 @@ public function loadMetadataForClass($className, ClassMetadata $class) $mapping = array('fieldName' => (string) $xmlRoot->node->attributes()->name); $this->addNodeMapping($class, $mapping); } + if (isset($xmlRoot->child)) { + $mapping = array('fieldName' => (string) $xmlRoot->child->attributes()->name); + $this->addChildMapping($class, $mapping); + } if (isset($xmlRoot->{'reference-many'})) { foreach ($xmlRoot->{'reference-many'} as $reference) { $this->addReferenceMapping($class, $reference, 'many'); @@ -116,6 +120,11 @@ private function addNodeMapping(ClassMetadata $class, $mapping) $class->mapNode($mapping); } + private function addChildMapping(ClassMetadata $class, $mapping) + { + $class->mapChild($mapping); + } + private function addReferenceMapping(ClassMetadata $class, $reference, $type) { $cascade = array_keys((array) $reference->cascade); diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php index a5ad4c1bd..7aff7e1ee 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php @@ -87,6 +87,10 @@ public function loadMetadataForClass($className, ClassMetadata $class) $mapping = array('fieldName' => $element['node']); $this->addNodeMapping($class, $mapping); } + if (isset($element['child'])) { + $mapping = array('fieldName' => $element['child']); + $this->addChildMapping($class, $mapping); + } if (isset($element['referenceOne'])) { foreach ($element['referenceOne'] as $fieldName => $reference) { $this->addMappingFromReference($class, $fieldName, $reference, 'one'); @@ -114,6 +118,11 @@ private function addNodeMapping(ClassMetadata $class, $mapping) $class->mapNode($mapping); } + private function addChildMapping(ClassMetadata $class, $mapping) + { + $class->mapChild($mapping); + } + private function addMappingFromReference(ClassMetadata $class, $fieldName, $reference, $type) { $mapping = array( diff --git a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php index de4267d99..ec927a604 100644 --- a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php +++ b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php @@ -141,8 +141,8 @@ public function createDocument($documentName, $node, array &$hints = array()) { // TODO create a doctrine: namespace and register node types with doctrine:name - if ($node->hasProperty('_doctrine_alias')) { - $metadata = $this->dm->getMetadataFactory()->getMetadataForAlias($node->getPropertyValue('_doctrine_alias')); + if ($node->hasProperty('phpcr:alias')) { + $metadata = $this->dm->getMetadataFactory()->getMetadataForAlias($node->getPropertyValue('phpcr:alias')); $type = $metadata->name; if (isset($documentName) && $this->dm->getConfiguration()->getValidateDoctrineMetadata()) { $validate = true; @@ -150,7 +150,7 @@ public function createDocument($documentName, $node, array &$hints = array()) } else if (isset($documentName)) { $type = $documentName; if ($this->dm->getConfiguration()->getWriteDoctrineMetadata()) { - $node->setProperty('_doctrine_alias', $documentName); + $node->setProperty('phpcr:alias', $documentName); } } else { throw new \InvalidArgumentException("Missing Doctrine metadata in the Document, cannot hydrate (yet)!"); @@ -228,6 +228,14 @@ public function createDocument($documentName, $node, array &$hints = array()) } } + foreach ($class->childMappings as $childName => $mapping) { + if ($node->hasNode($mapping['name'])) { + $documentState[$class->childMappings[$childName]['fieldName']] = $this->createDocument(null, $node->getNode($mapping['name'])); + } else { + $documentState[$class->childMappings[$childName]['fieldName']] = null; + } + } + if (isset($this->identityMap[$id])) { $document = $this->identityMap[$id]; $overrideLocalValues = false; @@ -317,7 +325,7 @@ private function doScheduleInsert($document, &$visited) break; } - $this->cascadeScheduleInsert($class, $document, $visited); + $this->cascadeScheduleInsert($class, $document, $visited, $path); } /** @@ -326,7 +334,7 @@ private function doScheduleInsert($document, &$visited) * @param object $document * @param array $visited */ - private function cascadeScheduleInsert($class, $document, &$visited) + private function cascadeScheduleInsert($class, $document, &$visited, $path) { foreach ($class->associationsMappings as $assocName => $assoc) { if ( ($assoc['cascade'] & ClassMetadata::CASCADE_PERSIST) ) { @@ -345,6 +353,12 @@ private function cascadeScheduleInsert($class, $document, &$visited) } } } + foreach ($class->childMappings as $childName => $mapping) { + $child = $class->reflFields[$childName]->getValue($document); + if ($child !== null && $this->getDocumentState($child) == self::STATE_NEW) { + $this->doScheduleInsert($child, $path.'/'.$mapping['name'], $visited); + } + } } private function getIdGenerator($type) @@ -366,6 +380,28 @@ public function scheduleRemove($document) } } + private function purgeChildren($document) + { + $class = $this->dm->getClassMetadata(get_class($document)); + foreach ($class->childMappings as $childName => $mapping) { + $child = $class->reflFields[$childName]->getValue($document); + if ($child !== null) { + $this->purgeChildren($child); + + $oid = spl_object_hash($child); + unset( + $this->scheduledRemovals[$oid], + $this->scheduledInserts[$oid], + $this->scheduledUpdates[$oid] + ); + + $this->removeFromIdentityMap($child); + } + } + + + } + public function getDocumentState($document) { $oid = spl_object_hash($document); @@ -434,7 +470,7 @@ public function computeChangeSet(ClassMetadata $class, $document) $changed = false; foreach ($actualData as $fieldName => $fieldValue) { - if (!isset($class->fieldMappings[$fieldName])) { + if (!isset($class->fieldMappings[$fieldName]) && !isset($class->childMappings[$fieldName])) { continue; } if ($class->isCollectionValuedAssociation($fieldName)) { @@ -517,6 +553,7 @@ public function flush() foreach ($this->scheduledRemovals as $oid => $document) { $this->nodesMap[$oid]->remove(); $this->removeFromIdentityMap($document); + $this->purgeChildren($document); } foreach ($this->scheduledInserts as $oid => $document) { @@ -529,6 +566,7 @@ public function flush() $path = $this->documentPaths[$oid]; $parentNode = $session->getNode(dirname($path) === '\\' ? '/' : dirname($path)); $node = $parentNode->addNode(basename($path), $class->nodeType); + $node->addMixin('phpcr:managed'); if ($class->isVersioned) { $node->addMixin("mix:versionable"); @@ -542,7 +580,7 @@ public function flush() $class->reflFields[$class->node]->setValue($document, $node); } if ($useDoctrineMetadata) { - $node->setProperty('_doctrine_alias', $class->alias, 'string'); + $node->setProperty('phpcr:alias', $class->alias, 'string'); } foreach ($this->documentChangesets[$oid] as $fieldName => $fieldValue) { @@ -568,7 +606,7 @@ public function flush() } if ($useDoctrineMetadata) { - $node->setProperty('_doctrine_alias', $class->alias); + $node->setProperty('phpcr:alias', $class->alias); } foreach ($this->documentChangesets[$oid] as $fieldName => $fieldValue) { @@ -600,8 +638,16 @@ public function flush() $data['doctrine_metadata']['associations'][$fieldName] = $ids; } } + // child is set to null ... remove the node ... + } else if (isset($class->childMappings[$fieldName]) && $fieldValue === null) { + if ($node->hasNode($class->childMappings[$fieldName]['name'])) { + $child = $node->getNode($class->childMappings[$fieldName]['name']); + $childDocument = $this->createDocument(null, $child); + $this->purgeChildren($childDocument); + $child->remove(); + } } - } + } // respect the non mapped data, otherwise they will be deleted. if (isset($this->nonMappedData[$oid]) && $this->nonMappedData[$oid]) { diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/ChildTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/ChildTest.php new file mode 100644 index 000000000..f46cf6ab8 --- /dev/null +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/ChildTest.php @@ -0,0 +1,198 @@ +type = 'Doctrine\Tests\ODM\PHPCR\Functional\TestObj'; + $this->childType = 'Doctrine\Tests\ODM\PHPCR\Functional\ChildTestObj'; + $this->dm = $this->createDocumentManager(); + $this->node = $this->resetFunctionalNode($this->dm); + } + + public function testCreate() + { + $parent = new TestObj(); + $child = new ChildTestObj(); + $parent->name = 'Parent'; + $parent->child = $child; + $child->name = 'Child'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $this->assertTrue($this->node->getNode('childtest')->hasNode('test')); + $this->assertEquals($this->node->getNode('childtest')->getNode('test')->getProperty('name')->getString(), 'Child'); + } + + public function testCreateWithoutChild() + { + $parent = new TestObj(); + $parent->name = 'Parent'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $this->assertFalse($this->node->getNode('childtest')->hasNode('test')); + } + + public function testUpdate() + { + $parent = new TestObj(); + $child = new ChildTestObj(); + $parent->name = 'Parent'; + $parent->child = $child; + $child->name = 'Child'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + $parent->child->name = 'Child changed'; + + $this->dm->persist($parent, $parent->path); + $this->dm->flush(); + $this->dm->clear(); + $this->assertNotEquals($this->node->getNode('childtest')->getNode('test')->getProperty('name')->getString(), 'Child'); + $this->assertEquals($this->node->getNode('childtest')->getNode('test')->getProperty('name')->getString(), 'Child changed'); + } + + public function testRemove1() + { + $parent = new TestObj(); + $child = new ChildTestObj(); + $parent->name = 'Parent'; + $parent->child = $child; + $child->name = 'Child'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + //$parent->child->name = 'Child changed'; + + $this->dm->remove($parent); + $this->dm->flush(); + $this->dm->clear(); + $parent = $this->dm->find($this->type, '/functional/childtest'); + $this->assertNull($parent); + } + + public function testRemove2() + { + $parent = new TestObj(); + $child = new ChildTestObj(); + $parent->name = 'Parent'; + $parent->child = $child; + $child->name = 'Child'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $child = $this->dm->find($this->childType, '/functional/childtest/test'); + + $this->dm->remove($child); + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + + $this->assertNull($parent->child); + $this->assertTrue($this->node->hasNode('childtest')); + $this->assertFalse($this->node->getNode('childtest')->hasNode('test')); + } + + public function testChildSetNull() + { + $parent = new TestObj(); + $child = new ChildTestObj(); + $parent->name = 'Parent'; + $parent->child = $child; + $child->name = 'Child'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + $parent->child->name = 'new name'; + $parent->child = null; + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + $this->assertNull($parent->child); + } + + /* this fails as the newChild is not persisted */ + public function testChildReplace() + { + $parent = new TestObj(); + $child = new ChildTestObj(); + $parent->name = 'Parent'; + $parent->child = $child; + $child->name = 'Child'; + + $this->dm->persist($parent, '/functional/childtest'); + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + $newChild = new ChildTestObj(); + $newChild->name = 'new name'; + $parent->child = $newChild; + $this->dm->flush(); + $this->dm->clear(); + + $parent = $this->dm->find($this->type, '/functional/childtest'); + $this->assertEquals($parent->child->name, 'new name'); + } +} + + +/** + * @Document(alias="childTestObj") + */ +class ChildTestObj +{ + /** @Path */ + public $path; + /** @Node */ + public $node; + /** @String */ + public $name; +} +/** + * @Document(alias="testObj") + */ +class TestObj +{ + /** @Path */ + public $path; + /** @Node */ + public $node; + /** @String */ + public $name; + /** @Child(name="test") */ + public $child; +} diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/FileTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/FileTest.php new file mode 100644 index 000000000..61d9e2ad4 --- /dev/null +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/FileTest.php @@ -0,0 +1,60 @@ +type = 'Doctrine\Tests\ODM\PHPCR\Functional\TestObj'; + $this->dm = $this->createDocumentManager(); + $this->node = $this->resetFunctionalNode($this->dm); + } + + public function testCreate() + { + $parent = new TestObj(); + $parent->file = new File(); + $parent->file->setFileContent('Doctrine/Tests/ODM/PHPCR/Functional/_files/foo.txt'); + + $this->dm->persist($parent, '/functional/filetest'); + $this->dm->flush(); + $this->dm->clear(); + + $this->assertTrue($this->node->getNode('filetest')->hasNode('file')); + $this->assertTrue($this->node->getNode('filetest')->getNode('file')->hasNode('jcr:content')); + $this->assertTrue($this->node->getNode('filetest')->getNode('file')->getNode('jcr:content')->hasProperty('jcr:data')); + } +} + + +/** + * @phpcr:Document(alias="testObj") + */ +class TestObj +{ + /** @phpcr:Path */ + public $path; + /** @phpcr:Node */ + public $node; + /** @phpcr:String */ + public $name; + /** @phpcr:Child */ + public $file; +} + diff --git a/tests/Doctrine/Tests/ODM/PHPCR/PHPCRFunctionalTestCase.php b/tests/Doctrine/Tests/ODM/PHPCR/PHPCRFunctionalTestCase.php index 4d57e8e8a..18e2ec780 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/PHPCRFunctionalTestCase.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/PHPCRFunctionalTestCase.php @@ -8,7 +8,10 @@ public function createDocumentManager() { $reader = new \Doctrine\Common\Annotations\AnnotationReader(); $reader->setDefaultAnnotationNamespace('Doctrine\ODM\PHPCR\Mapping\\'); - $paths = __DIR__ . "/../../Models"; + $reader->setAnnotationNamespaceAlias('Doctrine\ODM\PHPCR\Mapping\\', 'phpcr'); + $paths = array(); + $paths[] = __DIR__ . "/../../Models"; + $paths[] = __DIR__ . "/../../../../../lib/Doctrine/ODM/PHPCR/Document"; $metaDriver = new \Doctrine\ODM\PHPCR\Mapping\Driver\AnnotationDriver($reader, $paths); $url = isset($_GLOBALS['DOCTRINE_PHPCR_REPOSITORY']) ? $_GLOBALS['DOCTRINE_PHPCR_REPOSITORY'] : 'http://127.0.0.1:8080/server/'; @@ -40,4 +43,4 @@ public function resetFunctionalNode($dm) $session->save(); return $node; } -} \ No newline at end of file +}