Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.
  • Loading branch information...
commit 09c55913206b477f2120e09b47989d544a289ab1 1 parent d82f288
Uwe Jäger authored
View
15 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://<path to your repository>"
+ - enter "registernodetype <path to phpcr.cnd>"
+ - enter "quit"
+ - start your server again ...
+
+ where phpcr.cnd contains
+
+ <phpcr='http://www.doctrine-project.org/phpcr-odm'>
+ [phpcr:managed]
+ mixin
+ - phpcr:alias (STRING)
+
1. Define one of those mapping drivers
// Annotation driver
View
34 lib/Doctrine/ODM/PHPCR/Document/File.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Doctrine\ODM\PHPCR\Document;
+
+/**
+ * @phpcr:Document(alias="file", nodeType="nt:file")
+ */
+class File
+{
+ /** @phpcr:Path */
+ protected $path;
+
+ /** @phpcr:Node */
+ protected $node;
+
+ /** @phpcr:Date(name="jcr:created") */
+ protected $created;
+
+ /** @phpcr:String(name="jcr:createdBy") */
+ protected $createdBy;
+
+ /** @phpcr:Child(name="jcr:content") */
+ protected $content;
+
+ public function setFileContent($filename)
+ {
+ if ($this->content === null)
+ {
+ $this->content = new Resource();
+ }
+ $this->content->setFileData(file_get_contents($filename));
+ }
+
+}
View
23 lib/Doctrine/ODM/PHPCR/Document/Folder.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Doctrine\ODM\PHPCR\Document;
+
+/**
+ * @phpcr:Document(alias="folder", nodeType="nt:folder")
+ */
+class Folder
+{
+ /** @phpcr:Path */
+ protected $path;
+
+ /** @phpcr:Node */
+ protected $node;
+
+ /** @phpcr:Date(name="jcr:created") */
+ protected $created;
+
+ /** @phpcr:String(name="jcr:createdBy") */
+ protected $createdBy;
+}
+
+
View
37 lib/Doctrine/ODM/PHPCR/Document/Resource.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Doctrine\ODM\PHPCR\Document;
+
+/**
+ * @phpcr:Document(alias="resource", nodeType="nt:resource")
+ */
+class Resource
+{
+ /** @phpcr:Path */
+ protected $path;
+
+ /** @phpcr:Node */
+ protected $node;
+
+ /** @phpcr:Binary(name="jcr:data") */
+ protected $data;
+
+ /** @phpcr:String(name="jcr:mimeType") */
+ protected $mimeType;
+
+ /** @phpcr:String(name="jcr:encoding") */
+ protected $encoding;
+
+ /** @phpcr:Date(name="jcr:lastModified") */
+ protected $lastModified;
+
+ /** @phpcr:String(name="jcr:lastModifiedBy") */
+ protected $lastModifiedBy;
+
+
+ public function setFileData($data)
+ {
+ $this->data = $data;
+ }
+}
+
View
3  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) {
View
4 lib/Doctrine/ODM/PHPCR/Mapping/Driver/DoctrineAnnotations.php
@@ -82,3 +82,7 @@ class Reference extends Annotation
public $cascade = array();
public $mappedBy;
}
+class Child extends Annotation
+{
+ public $name;
+}
View
9 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);
View
9 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(
View
64 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]) {
View
198 tests/Doctrine/Tests/ODM/PHPCR/Functional/ChildTest.php
@@ -0,0 +1,198 @@
+<?php
+
+namespace Doctrine\Tests\ODM\PHPCR\Functional;
+
+/**
+ * @group functional
+ */
+class ChildTest extends \Doctrine\Tests\ODM\PHPCR\PHPCRFunctionalTestCase
+{
+ /**
+ * @var DocumentManager
+ */
+ private $dm;
+
+ private $type;
+ private $childType;
+
+ private $node;
+
+ public function setUp()
+ {
+ $this->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;
+}
View
60 tests/Doctrine/Tests/ODM/PHPCR/Functional/FileTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Doctrine\Tests\ODM\PHPCR\Functional;
+
+use Doctrine\ODM\PHPCR\Document\File;
+
+/**
+ * @group functional
+ */
+class FileTest extends \Doctrine\Tests\ODM\PHPCR\PHPCRFunctionalTestCase
+{
+ /**
+ * @var DocumentManager
+ */
+ private $dm;
+
+ private $type;
+ private $childType;
+
+ private $node;
+
+ public function setUp()
+ {
+ $this->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;
+}
+
View
7 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;
}
-}
+}
Please sign in to comment.
Something went wrong with that request. Please try again.