Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add weak reference path type with bc break #116

Merged
merged 3 commits into from

2 participants

@lsmith77
Owner

this PR builds on #113, but breaks BC by renaming the "weak" property to "strategy" and switching from a mice boolean/string to a string property.

@dbu
Collaborator
dbu commented

i still think it would be better to have an attribute on the PATH property mapping to have it dereference the path, to make the difference to (weak/strong) references more visible and map the phpcr model more closely. but if you are still convinced, i can live with this. before we merge and bc break i have to things i want to discuss:

  1. what if the PATH points to something not existing? of course it should not result in an exception, its like a weak reference. but will the property be null? or the path string (which would be confusing i guess). what happens when i save the node, do i lose the PATH property? if not, how can i delete it if i actually want to?

  2. from #113 but still valid: a PATH property can be set to a relative path (which reduces the problem of moves). i could say my node article (path /my/article) has a property "mainImage" which contains a path like "attachedImages/test.png". if i do getNode() on that property, it is resolved to "/my/article/attachedImages/test.png". move the article to /your/article and everything still works.
    when doing this in the odm, what path would we use? absolute path? that loses quite a bit of the power of the PATH property semantics. one thing we could do is strip the common part of the paths and then store a relative path from there, possible with ../ - but you can create cases where this is the wrong thing to do. should we add some hint for a strategy that creates relative paths?

  3. just an idea: we are somewhat inconsistent. for property mappings, we have multiple=true for multivalue. here we use ReferenceOne and ReferenceMany. those words come from the ORM world, where its a huge difference wheter you have one or several references. in phpcr, its just having multivalue or not, which is a minor detail. what about calling it Reference with the optional multiple=true ? might be not a good idea, just wanted to mention it as now would be a good time to change when we bc break anyways.

@lsmith77
Owner

Again imho I think you are over thinking this. This feature is an advanced performance optimization.

1) you can remove a node by simply calling remove() on the document instance. this is the same as with other references. i don't see the difference.

2) we could also adjust path properties when we do a move, but this would require recursing etc. so i wouldn't do anything there.

3) i have also thought about that, but imho its cleaner this way to make it clear that there is a lot of "special" logic here. same for @Child vs @Children

@lsmith77 lsmith77 Merge remote-tracking branch 'origin/master' into add_weak_reference_…
…path_type_with_BC_break

Conflicts:
	lib/Doctrine/ODM/PHPCR/ReferenceManyCollection.php
e555520
@dbu
Collaborator
dbu commented

1) this is a misunderstanding. i am talking about this situation:

  • repository contains only 1 node : /node1
  • node1 is the document, it has a ReferenceOne property with the path /node1/node3
  • this resolves to $doc->reference == null probably. what happens if i save the document? will i lose "/node1/node3" from my property? or will it be kept? what if i want to remove it?

2) agreed that adjusting the paths automatically sounds complicated. if you need it use uuid. but what about helping to have relative paths? bad idea?

3) agreed.

@lsmith77
Owner

1) i don't know what will happen in that case .. but i also do not know what will happen on that case with a uuid and a weak reference .. and if you want to remove the "reference" then just assign null?
2) what do you mean with "helping to have relative paths"? you mean being able to specify that the path should not be stored as an absolute path but instead as a relative path? i guess that would be a nice feature. we could handle that with a new "strategy". aka "relative_path"

@dbu
Collaborator
dbu commented

1) ok, then lets open a new issue for that. its the same problem actually with weak references. we should know what happens and think what makes sense. http://www.doctrine-project.org/jira/browse/PHPCR-58

2) exactly. except that i think a user might need to attach his own strategy to the document manager. we could call this path_relative= and provide "max" that string-compares the target path and the node path and puts as few ../ as necessary. but again this does not block this pull request. http://www.doctrine-project.org/jira/browse/PHPCR-59

so lets merge this. sorry for having been so insistent.

@dbu dbu merged commit 382ba38 into from
@lsmith77
Owner

ok great .. thx for opening those tickets

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 23, 2012
  1. @lsmith77
Commits on Feb 27, 2012
  1. @lsmith77
Commits on Mar 7, 2012
  1. @lsmith77

    Merge remote-tracking branch 'origin/master' into add_weak_reference_…

    lsmith77 authored
    …path_type_with_BC_break
    
    Conflicts:
    	lib/Doctrine/ODM/PHPCR/ReferenceManyCollection.php
This page is out of date. Refresh to see the latest.
View
15 README.md
@@ -436,18 +436,23 @@ class DocumentRepository extends BaseDocumentRepository implements RepositoryIdI
</td>
</tr>
<tr>
- <td>ReferenceOne(targetDocument="myDocument", weak=false): (*)</td>
+ <td>ReferenceOne(targetDocument="myDocument", strategy="weak"): (*)</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.
+ reference. By optionaly specifying strategy="hard" you get a hard reference.
+ Finally with strategy="path" it will simply store the path to the node,
+ but automatically dereference.
It is optional to specify the targetDocument, you can reference any
- document type.
+ document type. However using strategy="path" will be faster if a targetDocument
+ is set.
</td>
</tr>
<tr>
- <td> ReferenceMany(targetDocument="myDocument", weak=false): (*)</td>
+ <td> ReferenceMany(targetDocument="myDocument", weak="weak"): (*)</td>
<td>Same as ReferenceOne except that you can refer many documents with the
same document and reference type. If you dont't specify targetDocument
- you can reference documents of mixed types in the same property.
+ you can reference documents of mixed types in the same property. This
+ type of collection will always be lazy loaded regardless of the strategy
+ chosen.
</td>
</tr>
<tr>
View
4 lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php
@@ -205,8 +205,8 @@ class Reference
{
/** @var string */
public $targetDocument;
- /** @var boolean */
- public $weak = true;
+ /** @var string */
+ public $strategy = 'weak';
}
/**
* @Annotation
View
9 lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
@@ -549,7 +549,7 @@ 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'.");
+ throw new MappingException("You have to specify a 'referenceType' for the '" . $this->name . "' association which must be null, 'weak' or 'hard': ".$mapping['referenceType']);
}
return $mapping;
}
@@ -603,8 +603,8 @@ protected function validateAndCompleteAssociationMapping($mapping)
if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
$mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
}
- if (isset($mapping['weak']) && !is_bool($mapping['weak'])) {
- throw new MappingException("The attribute 'weak' for the '" . $this->name . "' association has to be a boolean true or false.");
+ if (isset($mapping['strategy']) && !in_array($mapping['strategy'], array(null, 'weak', 'hard', 'path'))) {
+ throw new MappingException("The attribute 'strategy' for the '" . $this->name . "' association has to be either a null, 'weak', 'hard' or 'path': ".$mapping['strategy']);
}
return $mapping;
}
@@ -627,6 +627,9 @@ public function mapManyToMany($mapping)
public function storeAssociationMapping($mapping)
{
+ if (empty($mapping['strategy'])) {
+ $mapping['strategy'] = 'weak';
+ }
$this->associationsMappings[$mapping['fieldName']] = $mapping;
}
View
27 lib/Doctrine/ODM/PHPCR/ReferenceManyCollection.php
@@ -12,21 +12,20 @@
*/
class ReferenceManyCollection extends MultivaluePropertyCollection
{
-
- private $referencedDocUUIDs;
+ private $referencedNodes;
private $targetDocument = null;
/**
* Creates a new persistent collection.
*
* @param DocumentManager $dm The DocumentManager the collection will be associated with.
- * @param array $referencedDocUUIDs An array of referenced UUIDs
+ * @param array $referencedNodes An array of referenced nodes (UUID or path)
* @param string $targetDocument the objectname of the target documents
*/
- public function __construct(DocumentManager $dm, array $referencedDocUUIDs, $targetDocument)
+ public function __construct(DocumentManager $dm, array $referencedNodes, $targetDocument)
{
$this->dm = $dm;
- $this->referencedDocUUIDs = $referencedDocUUIDs;
+ $this->referencedNodes = $referencedNodes;
$this->targetDocument = $targetDocument;
}
@@ -40,25 +39,31 @@ public function initialize()
$this->initialized = true;
$referencedDocs = array();
- $referencedNodes = $this->dm->getPhpcrSession()->getNodesByIdentifier($this->referencedDocUUIDs);
+ $referencedNodes = $this->dm->getPhpcrSession()->getNodesByIdentifier($this->referencedNodes);
$uow = $this->dm->getUnitOfWork();
+
+ $referencedClass = $this->targetDocument
+ ? $this->dm->getMetadataFactory()->getMetadataFor(ltrim($this->targetDocument, '\\'))->name
+ : null;
+
foreach ($referencedNodes as $referencedNode) {
- $referencedClass = $this->targetDocument ? $this->dm->getMetadataFactory()->getMetadataFor(ltrim($this->targetDocument, '\\'))->name : null;
- $proxy = $referencedClass ? $uow->createProxy($referencedNode->getPath(), $referencedClass) : $uow->createProxyFromNode($referencedNode);
+ $proxy = $referencedClass
+ ? $uow->createProxy($referencedNode->getPath(), $referencedClass)
+ : $uow->createProxyFromNode($referencedNode);
$referencedDocs[] = $proxy;
}
$this->collection = new ArrayCollection($referencedDocs);
}
}
-
+
public function count()
{
- return count($this->referencedDocUUIDs);
+ return count($this->referencedNodes);
}
public function isEmpty()
{
- return ($this->count() == 0);
+ return !$this->count();
}
}
View
82 lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -276,7 +276,9 @@ public function createDocument($className, $node, array &$hints = array())
continue;
}
- if ($assocOptions['type'] & ClassMetadata::MANY_TO_ONE) {
+ if ($assocOptions['type'] & ClassMetadata::MANY_TO_ONE
+ && $assocOptions['strategy'] !== 'path'
+ ) {
$refNodeUUIDs[] = $node->getProperty($assocOptions['fieldName'])->getString();
}
}
@@ -292,23 +294,35 @@ public function createDocument($className, $node, array &$hints = array())
continue;
}
- $referencedNode = $node->getPropertyValue($assocOptions['fieldName']);
- $referencedClass = isset($assocOptions['targetDocument'])
- ? $this->dm->getMetadataFactory()->getMetadataFor(ltrim($assocOptions['targetDocument'], '\\'))->name : null;
- $proxy = $referencedClass
- ? $this->createProxy($referencedNode->getPath(), $referencedClass)
- : $this->createProxyFromNode($referencedNode);
+ if (isset($assocOptions['targetDocument'])) {
+ $referencedClass = $this->dm->getMetadataFactory()->getMetadataFor(ltrim($assocOptions['targetDocument'], '\\'))->name;
+
+ if ($assocOptions['strategy'] === 'path') {
+ $path = $node->getProperty($assocOptions['fieldName'])->getString();
+ } else {
+ $referencedNode = $node->getPropertyValue($assocOptions['fieldName']);
+ $path = $referencedNode->getPath();
+ }
+
+ $proxy = $this->createProxy($path, $referencedClass);
+ } else {
+ $referencedNode = $node->getPropertyValue($assocOptions['fieldName']);
+ $proxy = $this->createProxyFromNode($referencedNode);
+ }
+
$documentState[$class->associationsMappings[$assocName]['fieldName']] = $proxy;
} elseif ($assocOptions['type'] & ClassMetadata::MANY_TO_MANY) {
if (!$node->hasProperty($assocOptions['fieldName'])) {
continue;
}
- $referencedDocUUIDs = array();
- foreach ($node->getProperty($assocOptions['fieldName'])->getString() as $uuid) {
- $referencedDocUUIDs[] = $uuid;
+
+ $referencedNodes = array();
+ foreach ($node->getProperty($assocOptions['fieldName'])->getString() as $reference) {
+ $referencedNodes[] = $reference;
}
- if (count($referencedDocUUIDs) > 0) {
- $coll = new ReferenceManyCollection($this->dm, $referencedDocUUIDs, $assocOptions['targetDocument']);
+
+ if (count($referencedNodes) > 0) {
+ $coll = new ReferenceManyCollection($this->dm, $referencedNodes, $assocOptions['targetDocument']);
$documentState[$class->associationsMappings[$assocName]['fieldName']] = $coll;
}
}
@@ -1298,8 +1312,17 @@ private function executeUpdates($documents, $dispatchEvents = true)
continue;
}
- $type = $class->associationsMappings[$fieldName]['weak']
- ? PropertyType::WEAKREFERENCE : PropertyType::REFERENCE;
+ switch ($class->associationsMappings[$fieldName]['strategy']) {
+ case 'hard':
+ $strategy = PropertyType::REFERENCE;
+ break;
+ case 'path':
+ $strategy = PropertyType::PATH;
+ break;
+ default:
+ $strategy = PropertyType::WEAKREFERENCE;
+ break;
+ }
if ($class->associationsMappings[$fieldName]['type'] === $class::MANY_TO_MANY) {
if (isset($fieldValue)) {
@@ -1310,27 +1333,36 @@ private function executeUpdates($documents, $dispatchEvents = true)
}
$associatedNode = $this->session->getNode($this->getDocumentId($fv));
- $refClass = $this->dm->getClassMetadata(get_class($fv));
- $this->setMixins($refClass, $associatedNode);
- if (!$associatedNode->isNodeType('mix:referenceable')) {
- throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: '.self::objToStr($document, $this->dm), get_class($fv)));
+ if ($strategy === PropertyType::PATH) {
+ $refNodesIds[] = $associatedNode->getPath();
+ } else {
+ $refClass = $this->dm->getClassMetadata(get_class($fv));
+ $this->setMixins($refClass, $associatedNode);
+ if (!$associatedNode->isNodeType('mix:referenceable')) {
+ throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: '.self::objToStr($document, $this->dm), get_class($fv)));
+ }
+ $refNodesIds[] = $associatedNode->getIdentifier();
}
- $refNodesIds[] = $associatedNode->getIdentifier();
}
if (!empty($refNodesIds)) {
- $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $refNodesIds, $type);
+ $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $refNodesIds, $strategy);
}
}
} elseif ($class->associationsMappings[$fieldName]['type'] === $class::MANY_TO_ONE) {
if (isset($fieldValue)) {
$associatedNode = $this->session->getNode($this->getDocumentId($fieldValue));
- $refClass = $this->dm->getClassMetadata(get_class($fieldValue));
- $this->setMixins($refClass, $associatedNode);
- if (!$associatedNode->isNodeType('mix:referenceable')) {
- throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: '.self::objToStr($document, $this->dm), get_class($fieldValue)));
+
+ if ($strategy === PropertyType::PATH) {
+ $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $associatedNode->getPath(), $strategy);
+ } else {
+ $refClass = $this->dm->getClassMetadata(get_class($fieldValue));
+ $this->setMixins($refClass, $associatedNode);
+ if (!$associatedNode->isNodeType('mix:referenceable')) {
+ throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: '.self::objToStr($document, $this->dm), get_class($fieldValue)));
+ }
+ $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $associatedNode->getIdentifier(), $strategy);
}
- $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $associatedNode->getIdentifier(), $type);
}
}
} elseif (isset($class->childMappings[$fieldName])) {
View
2  tests/Doctrine/Tests/Models/References/HardRefTestObj.php
@@ -11,7 +11,7 @@ class HardRefTestObj
{
/** @PHPCRODM\Id */
public $id;
- /** @PHPCRODM\ReferenceOne(targetDocument="RefRefTestObj", weak=false) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="RefRefTestObj", strategy="hard") */
public $reference;
/** @PHPCRODM\String */
public $name;
View
18 tests/Doctrine/Tests/Models/References/RefTestObjByPath.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Doctrine\Tests\Models\References;
+
+use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
+
+/**
+ * @PHPCRODM\Document()
+ */
+class RefTestObjByPath
+{
+ /** @PHPCRODM\Id */
+ public $id;
+ /** @PHPCRODM\ReferenceOne(targetDocument="RefRefTestObj", strategy="path") */
+ public $reference;
+ /** @PHPCRODM\String */
+ public $name;
+}
View
2  tests/Doctrine/Tests/Models/References/WeakRefTestObj.php
@@ -11,7 +11,7 @@ class WeakRefTestObj
{
/** @PHPCRODM\Id */
public $id;
- /** @PHPCRODM\ReferenceOne(targetDocument="RefRefTestObj", weak=true) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="RefRefTestObj", strategy="weak") */
public $reference;
/** @PHPCRODM\String */
public $name;
View
29 tests/Doctrine/Tests/ODM/PHPCR/Functional/ReferenceTest.php
@@ -5,6 +5,7 @@
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
use Doctrine\Tests\Models\References\RefTestObj;
+use Doctrine\Tests\Models\References\RefTestObjByPath;
use Doctrine\Tests\Models\References\RefRefTestObj;
use Doctrine\Tests\Models\References\RefTestPrivateObj;
use Doctrine\Tests\Models\References\RefManyTestObj;
@@ -12,6 +13,8 @@
use Doctrine\ODM\PHPCR\PHPCRException;
+use PHPCR\Util\UUIDHelper;
+
/**
* @group functional
*/
@@ -64,6 +67,32 @@ public function testCreate()
$this->assertEquals($this->session->getNode('/functional')->getNode('refTestObj')->getProperty('reference')->getValue(), $this->session->getNode('/functional')->getNode('refRefTestObj'));
$this->assertEquals($this->session->getNode('/functional')->getProperty('refTestObj/reference')->getString(), $this->session->getNode('/functional')->getNode('refRefTestObj')->getIdentifier());
+ $this->assertTrue(UUIDHelper::isUUID($this->session->getNode('/functional')->getProperty('refTestObj/reference')->getString()));
+ }
+
+ public function testCreateByPath()
+ {
+ $refTestObj = new RefTestObjByPath();
+ $refRefTestObj = new RefRefTestObj();
+
+ $refTestObj->id = "/functional/refTestObj";
+ $refRefTestObj->id = "/functional/refRefTestObj";
+ $refRefTestObj->name = "referenced";
+
+ $refTestObj->reference = $refRefTestObj;
+
+ $this->dm->persist($refTestObj);
+ $this->dm->flush();
+ $this->dm->clear();
+
+ $this->assertTrue($this->session->getNode('/functional')->hasNode('refRefTestObj'));
+ $this->assertEquals($this->session->getNode('/functional')->getNode('refRefTestObj')->getProperty('name')->getString(), 'referenced');
+
+ $this->assertTrue($this->session->getNode('/functional')->getNode('refTestObj')->hasProperty('reference'));
+ $this->assertEquals($this->session->getNode('/functional')->getNode('refTestObj')->getProperty('reference')->getValue(), "/functional/refRefTestObj");
+
+ $this->assertEquals($this->session->getNode('/functional')->getProperty('refTestObj/reference')->getString(), $this->session->getNode('/functional')->getNode('refRefTestObj')->getPath());
+ $this->assertFalse(UUIDHelper::isUUID($this->session->getNode('/functional')->getProperty('refTestObj/reference')->getString()));
}
public function testCreatePrivate()
View
15 tests/Doctrine/Tests/ODM/PHPCR/Functional/ReferrerTest.php
@@ -541,11 +541,11 @@ class HardReferrerTestObj
{
/** @PHPCRODM\Id */
public $id;
- /** @PHPCRODM\ReferenceOne(targetDocument="HardReferrerRefTestObj", weak=false) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="HardReferrerRefTestObj", strategy="hard") */
public $referenceToHard;
- /** @PHPCRODM\ReferenceOne(targetDocument="WeakReferrerRefTestObj", weak=false) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="WeakReferrerRefTestObj", strategy="hard") */
public $referenceToWeak;
- /** @PHPCRODM\ReferenceOne(targetDocument="AllReferrerRefTestObj", weak=false) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="AllReferrerRefTestObj", strategy="hard") */
public $referenceToAll;
/** @PHPCRODM\String */
public $name;
@@ -558,11 +558,14 @@ class WeakReferrerTestObj
{
/** @PHPCRODM\Id */
public $id;
- /** @PHPCRODM\ReferenceOne(targetDocument="WeakReferrerRefTestObj", weak=true) */
+ /**
+ * Should implicitly default to strategy="weak"
+ * @PHPCRODM\ReferenceOne(targetDocument="WeakReferrerRefTestObj")
+ */
public $referenceToWeak;
- /** @PHPCRODM\ReferenceOne(targetDocument="HardReferrerRefTestObj", weak=true) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="HardReferrerRefTestObj", strategy="weak") */
public $referenceToHard;
- /** @PHPCRODM\ReferenceOne(targetDocument="AllReferrerRefTestObj", weak=true) */
+ /** @PHPCRODM\ReferenceOne(targetDocument="AllReferrerRefTestObj", strategy="weak") */
public $referenceToAll;
/** @PHPCRODM\String */
public $name;
View
6 tests/Doctrine/Tests/ODM/PHPCR/Mapping/ClassMetadataTest.php
@@ -132,10 +132,7 @@ public function testSerialize($cm)
$this->assertEquals($expected, serialize($cm));
}
- /**
- * @depends testClassName
- */
- public function testUnserialize($cm)
+ public function testUnserialize()
{
$cm = unserialize('O:40:"Doctrine\ODM\PHPCR\Mapping\ClassMetadata":11:{s:13:"fieldMappings";a:0:{}s:10:"identifier";N;s:4:"name";s:39:"Doctrine\Tests\ODM\PHPCR\Mapping\Person";s:9:"namespace";s:32:"Doctrine\Tests\ODM\PHPCR\Mapping";s:16:"generatorOptions";a:0:{}s:11:"idGenerator";i:2;s:25:"customRepositoryClassName";s:25:"customRepositoryClassName";s:18:"isMappedSuperclass";b:1;s:11:"versionable";b:1;s:12:"versionField";N;s:18:"lifecycleCallbacks";a:1:{s:5:"event";a:1:{i:0;s:8:"callback";}}}');
@@ -162,6 +159,7 @@ public function testMapAssociationManyToOne($cm)
'targetDocument' => 'Doctrine\Tests\ODM\PHPCR\Mapping\Address',
'sourceDocument' => 'Doctrine\Tests\ODM\PHPCR\Mapping\Person',
'type' => ClassMetadata::MANY_TO_ONE,
+ 'strategy' => 'weak',
), $cm->associationsMappings['address']);
return $cm;
Something went wrong with that request. Please try again.