Permalink
Browse files

Merge pull request #59 from starkj/referrers

Referrers
  • Loading branch information...
dbu committed Aug 29, 2011
2 parents 9df0d32 + c40787c commit b554c05ed4d91e3b9f50b7ef06de003a13dc0783
View
@@ -158,6 +158,9 @@ Available annotations
<tr><td> Node: </td><td>The PHPCR NodeInterface instance for direct access. (This is subject to be removed when we have mapped all functionality you can get from the PHPCR node. </td></tr>
<tr><td> Child(name=x): </td><td>Map the child with name x to this property. </td></tr>
<tr><td> Children(filter=x): </td><td>Map the collection of children with matching name to this property. Filter is optional and works like the parameter in PHPCR Node::getNodes() (see the <a href="http://phpcr.github.com/doc/html/phpcr/nodeinterface.html#getNodes()">API</a>)</td></tr>
+<tr><td> ReferenceOne(targetDocument="myDocument", weak=false): </td><td>Refers a document of the type myDocument. The default is a weak reference. By optionaly specifying weak=false you get a hard reference.</td></tr>
+<tr><td> ReferenceMany(targetDocument="myDocument", weak=false): </td><td>Same as ReferenceOne except that you can refer many documents with the same document and reference type.</td></tr>
+<tr><td> Referrers(filterName="x", referenceType=null): </td><td>A property of this type stores documents that refer this document. filterName is optional. Its value is passed to the name parameter of <a href="http://phpcr.github.com/doc/html/phpcr/nodeinterface.html#getWeakReferences%28%29">Node::getReferences()<a/> or <a href="http://phpcr.github.com/doc/html/phpcr/nodeinterface.html#getWeakReferences%28%29">Node::getWeakReferences()</a>. You can also specify an optional referenceType, weak or hard, to only get documents that have either a weak or a hard reference to this document. If you specify null then all documents with weak or hard references are fetched, which is also the default behavior.</td></tr>
<tr><td> Property: </td><td>A property of the node, without specified type. </td></tr>
<tr><td> Boolean, <br />
Int, <br />
@@ -317,6 +317,20 @@ public function getChildren($document, $filter = null)
return $this->unitOfWork->getChildren($document, $filter);
}
+ /**
+ * Get the documents that refer a given document using an optional name.
+ *
+ * This methods gets all nodes as a collection of documents that refer the
+ * given document and matches a given name.
+ * @param $document document instance which referrers should be loaded
+ * @param string|array $name optional name to match on referrers names
+ * @return a collection of referrer documents
+ */
+ public function getReferrers($document, $type = null, $name = null)
+ {
+ return $this->unitOfWork->getReferrers($document, $type, $name);
+ }
+
/**
* Flush all current changes, that is save them within the phpcr session
* and commit that session to permanent storage.
@@ -92,6 +92,11 @@ class Children extends Annotation
{
public $filter = null;
}
+class Referrers extends Annotation
+{
+ public $filterName = null;
+ public $referenceType = null;
+}
final class EmbeddedDocument extends Annotation {}
final class EmbedOne extends Property {}
@@ -191,6 +191,11 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $childrenMappings = array();
+ /**
+ * Mapping of referrers: access referrer nodes through a collection
+ */
+ public $referrersMappings = array();
+
/**
* PHPCR documents are always versioned, this flag determines if this version is exposed to the userland.
*
@@ -520,6 +525,22 @@ public function mapChildren(array $mapping)
$this->childrenMappings[$mapping['fieldName']] = $mapping;
}
+ public function mapReferrers(array $mapping)
+ {
+ $mapping = $this->validateAndCompleteReferrersMapping($mapping, false);
+ $mapping['name'] = $mapping['fieldName'];
+ $this->referrersMappings[$mapping['fieldName']] = $mapping;
+ }
+
+ protected function validateAndCompleteReferrersMapping($mapping)
+ {
+ $mapping = $this->validateAndCompleteFieldMapping($mapping, false);
+ if (!(array_key_exists('referenceType', $mapping) && in_array($mapping['referenceType'], array(null, "weak", "hard")))) {
+ throw new MappingException("You have to specify a 'referenceType' for the '" . $this->name . "' association which must be null, 'weak' or 'hard'.");
+ }
+ return $mapping;
+ }
+
protected function validateAndCompleteFieldMapping($mapping, $isField = true)
{
if (!isset($mapping['fieldName'])) {
@@ -532,6 +553,7 @@ protected function validateAndCompleteFieldMapping($mapping, $isField = true)
|| isset($this->associationsMappings[$mapping['fieldName']])
|| isset($this->childMappings[$mapping['fieldName']])
|| isset($this->childrenMappings[$mapping['fieldName']])
+ || isset($this->referrersMappings[$mapping['fieldName']])
) {
throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
}
@@ -174,6 +174,9 @@ public function loadMetadataForClass($className, ClassMetadata $class)
} elseif ($fieldAnnot instanceof \Doctrine\ODM\PHPCR\Mapping\Annotations\ReferenceMany) {
$mapping = array_merge($mapping, (array) $fieldAnnot);
$class->mapManyToMany($mapping);
+ } elseif ($fieldAnnot instanceof \Doctrine\ODM\PHPCR\Mapping\Annotations\Referrers) {
+ $mapping = array_merge($mapping, (array) $fieldAnnot);
+ $class->mapReferrers($mapping);
}
}
}
@@ -174,6 +174,21 @@ private function getUnsetAttributes(ClassMetadata $class)
$attributes .= ", ";
}
+ foreach ($class->referrersMappings as $field) {
+ $attributes .= '$this->'.$field["fieldName"];
+ $attributes .= ", ";
+ }
+
+ foreach ($class->childrenMappings as $field) {
+ $attributes .= '$this->'.$field["fieldName"];
+ $attributes .= ", ";
+ }
+
+ foreach ($class->childMappings as $field) {
+ $attributes .= '$this->'.$field["fieldName"];
+ $attributes .= ", ";
+ }
+
$attributes = substr($attributes, 0, -2);
return "unset(".$attributes.");";
@@ -0,0 +1,32 @@
+<?php
+
+namespace Doctrine\ODM\PHPCR;
+
+/**
+ * Referrer collection class
+ *
+ * This class represents a collection of referrers of a document which phpcr
+ * names match a optional name
+ *
+ */
+class ReferrersCollection extends PersistentCollection
+{
+ private $document;
+ private $dm;
+ private $name;
+
+ public function __construct($document, DocumentManager $dm, $type = null, $name = null)
+ {
+ $this->document = $document;
+ $this->dm = $dm;
+ $this->type = $type;
+ $this->name = $name;
+ }
+
+ protected function load()
+ {
+ if (null === $this->col) {
+ $this->col = $this->dm->getReferrers($this->document, $this->type, $this->name);
+ }
+ }
+}
@@ -238,8 +238,10 @@ public function createDocument($documentName, $node, array &$hints = array())
}
}
- $session = $this->dm->getPhpcrSession();
- $refNodes = $session->getNodesByIdentifier($refNodeUUIDs);
+ if (count($refNodeUUIDs) > 0) {
+ $session = $this->dm->getPhpcrSession();
+ $refNodes = $session->getNodesByIdentifier($refNodeUUIDs);
+ }
// initialize inverse side collections
foreach ($class->associationsMappings as $assocName => $assocOptions) {
@@ -272,7 +274,6 @@ public function createDocument($documentName, $node, array &$hints = array())
$proxyOid = spl_object_hash($proxyDocument);
$this->nodesMap[$proxyOid] = $referencedNode;
}
-
} elseif ($assocOptions['type'] & ClassMetadata::MANY_TO_MANY) {
if (! $node->hasProperty($assocOptions['fieldName'])) {
continue;
@@ -338,6 +339,10 @@ public function createDocument($documentName, $node, array &$hints = array())
$documentState[$mapping['fieldName']] = new ChildrenCollection($document, $this->dm, $mapping['filter']);
}
+ foreach ($class->referrersMappings as $mapping) {
+ $documentState[$mapping['fieldName']] = new ReferrersCollection($document, $this->dm, $mapping['referenceType'], $mapping['filterName']);
+ }
+
if (isset($documentName) && $this->validateDocumentName && !($document instanceof $documentName)) {
$msg = "Doctrine metadata mismatch! Requested type '$documentName' type does not match type '{$class->name}' stored in the metadata";
throw new \InvalidArgumentException($msg);
@@ -448,6 +453,7 @@ 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) {
@@ -457,6 +463,13 @@ private function cascadeScheduleInsert($class, $document, &$visited)
$this->doScheduleInsert($child, $visited, ClassMetadata::GENERATOR_TYPE_ASSIGNED);
}
}
+
+ foreach ($class->referrersMappings as $referrerName => $mapping) {
+ $referrer = $class->reflFields[$referrerName]->getValue($document);
+ if ($referrer !== null && $this->getDocumentState($referrer) == self::STATE_NEW) {
+ $this->doScheduleInsert($referrer, $visited);
+ }
+ }
}
private function getIdGenerator($type)
@@ -505,8 +518,6 @@ private function purgeChildren($document)
$this->removeFromIdentityMap($child);
}
}
-
-
}
public function getDocumentState($document)
@@ -577,6 +588,7 @@ public function computeChangeSet(ClassMetadata $class, $document)
if (!isset($class->fieldMappings[$fieldName])
&& !isset($class->childMappings[$fieldName])
&& !isset($class->associationsMappings[$fieldName])
+ && !isset($class->referrersMappings[$fieldName])
) {
continue;
}
@@ -622,6 +634,14 @@ public function computeChangeSet(ClassMetadata $class, $document)
}
}
}
+
+ foreach ($class->referrersMappings as $name => $referrerMapping) {
+ if ($this->originalData[$oid][$name]) {
+ foreach ($this->originalData[$oid][$name] as $referrer) {
+ $this->computeReferrerChanges($referrerMapping, $referrer, $id);
+ }
+ }
+ }
}
/**
@@ -665,6 +685,25 @@ private function computeReferenceChanges($mapping, $reference, $referrerId)
}
}
+ /**
+ * Computes the changes of a referrer.
+ *
+ * @param mixed $referrer the referenced document.
+ */
+ private function computeReferrerChanges($mapping, $referrer, $referenceId)
+ {
+ $targetClass = $this->dm->getClassMetadata(get_class($referrer));
+ $state = $this->getDocumentState($referrer);
+ $oid = spl_object_hash($referrer);
+ if ($state == self::STATE_NEW) {
+ $this->persistNew($targetClass, $referrer, ClassMetadata::GENERATOR_TYPE_ASSIGNED);
+ $this->computeChangeSet($targetClass, $referrer);
+ } else if ($state == self::STATE_DETACHED) {
+ // TODO: can this actually happen?
+ throw new \InvalidArgumentException("A detached document was found through a "
+ . "referrer during cascading a persist operation.");
+ }
+ }
/**
* Gets the changeset for an document.
@@ -1103,6 +1142,52 @@ public function getChildren($document, $filter = null)
return new ArrayCollection($childDocuments);
}
+ /**
+ * Get all the documents that refer a given document using an optional name
+ * and an optional reference type.
+ *
+ * This methods gets all nodes as a collection of documents that refer (weak
+ * and hard) the given document. The property of the referrer node that referes
+ * the document needs to match the given name and must store a reference of the
+ * given type.
+ * @param $document document instance which referrers should be loaded
+ * @param string $type optional type of the reference the referrer should have
+ * ("weak" or "hard")
+ * @param string $name optional name to match on referrers reference property
+ * name
+ * @return a collection of referrer documents
+ */
+ public function getReferrers($document, $type = null, $name = null)
+ {
+ $oid = spl_object_hash($document);
+ $node = $this->nodesMap[$oid];
+
+ $referrerDocuments = array();
+ $referrerPropertiesW = array();
+ $referrerPropertiesH = array();
+
+ if ($type === null) {
+ $referrerPropertiesW = $node->getWeakReferences($name);
+ $referrerPropertiesH = $node->getReferences($name);
+ } elseif ($type === "weak") {
+ $referrerPropertiesW = $node->getWeakReferences($name);
+ } elseif ($type === "hard") {
+ $referrerPropertiesH = $node->getReferences($name);
+ }
+
+ foreach ($referrerPropertiesW as $referrerProperty) {
+ $referrerNode = $referrerProperty->getParent();
+ $referrerDocuments[] = $this->createDocument(null, $referrerNode);
+ }
+
+ foreach ($referrerPropertiesH as $referrerProperty) {
+ $referrerNode = $referrerProperty->getParent();
+ $referrerDocuments[] = $this->createDocument(null, $referrerNode);
+ }
+
+ return new ArrayCollection($referrerDocuments);
+ }
+
/**
* Get the PHPCR revision of the document that was current upon retrieval.
*
@@ -236,8 +236,33 @@ public function testModificationAfterPersist()
$this->assertNotNull($parent->child);
$this->assertEquals('Changed', $parent->child->name);
}
-}
+ public function testChildOfReference()
+ {
+ $referrerTestObj = new ChildReferrerTestObj();
+ $referrerTestObj->id = "/functional/referrerTestObj";
+ $referrerTestObj->name = "referrerTestObj";
+
+ $refererenceableTestObj = new ChildReferenceableTestObj();
+ $refererenceableTestObj->id = "/functional/referenceableTestObj";
+ $refererenceableTestObj->name = "referenceableTestObj";
+ $referrerTestObj->reference = $refererenceableTestObj;
+
+ $this->dm->persist($referrerTestObj);
+
+ $ChildTestObj = new ChildTestObj();
+ $ChildTestObj->id = "/functional/referenceableTestObj/test";
+ $ChildTestObj->name= "childTestObj";
+
+ $this->dm->persist($ChildTestObj);
+ $this->dm->flush();
+ $this->dm->clear();
+
+ $referrer = $this->dm->find(null, "/functional/referrerTestObj");
+
+ $this->assertEquals($referrer->reference->aChild->name, "childTestObj");
+ }
+}
/**
* @PHPCRODM\Document(alias="childTestObj")
@@ -251,6 +276,7 @@ class ChildChildTestObj
/** @PHPCRODM\String */
public $name;
}
+
/**
* @PHPCRODM\Document(alias="testObj")
*/
@@ -265,3 +291,33 @@ class ChildTestObj
/** @PHPCRODM\Child(name="test") */
public $child;
}
+
+/**
+ * @PHPCRODM\Document(alias="ChildReferrerTestObj")
+ */
+class ChildReferrerTestObj
+{
+ /** @PHPCRODM\Id */
+ public $id;
+
+ /** @PHPCRODM\String */
+ public $name;
+
+ /** @PHPCRODM\ReferenceOne(targetDocument="ChildReferenceableTestObj") */
+ public $reference;
+}
+
+/**
+ * @PHPCRODM\Document(alias="ChildReferenceableTestObj", referenceable="true")
+ */
+class ChildReferenceableTestObj
+{
+ /** @PHPCRODM\Id */
+ public $id;
+
+ /** @PHPCRODM\String */
+ public $name;
+
+ /** @PHPCRODM\Child(name="test") */
+ public $aChild;
+}
Oops, something went wrong.

0 comments on commit b554c05

Please sign in to comment.