Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add weak reference path type with bc break #116

Merged
merged 3 commits into from Mar 7, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Expand Up @@ -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>
Expand Down
Expand Up @@ -205,8 +205,8 @@ class Reference
{
/** @var string */
public $targetDocument;
/** @var boolean */
public $weak = true;
/** @var string */
public $strategy = 'weak';
}
/**
* @Annotation
Expand Down
9 changes: 6 additions & 3 deletions lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -627,6 +627,9 @@ public function mapManyToMany($mapping)

public function storeAssociationMapping($mapping)
{
if (empty($mapping['strategy'])) {
$mapping['strategy'] = 'weak';
}
$this->associationsMappings[$mapping['fieldName']] = $mapping;
}

Expand Down
27 changes: 16 additions & 11 deletions lib/Doctrine/ODM/PHPCR/ReferenceManyCollection.php
Expand Up @@ -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;
}

Expand All @@ -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();
}
}
82 changes: 57 additions & 25 deletions lib/Doctrine/ODM/PHPCR/UnitOfWork.php
Expand Up @@ -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();
}
}
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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)) {
Expand All @@ -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])) {
Expand Down
2 changes: 1 addition & 1 deletion tests/Doctrine/Tests/Models/References/HardRefTestObj.php
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions 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;
}
2 changes: 1 addition & 1 deletion tests/Doctrine/Tests/Models/References/WeakRefTestObj.php
Expand Up @@ -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;
Expand Down
29 changes: 29 additions & 0 deletions tests/Doctrine/Tests/ODM/PHPCR/Functional/ReferenceTest.php
Expand Up @@ -5,13 +5,16 @@
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;
use Doctrine\Tests\Models\References\RefManyTestObjForCascade;

use Doctrine\ODM\PHPCR\PHPCRException;

use PHPCR\Util\UUIDHelper;

/**
* @group functional
*/
Expand Down Expand Up @@ -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()
Expand Down
15 changes: 9 additions & 6 deletions tests/Doctrine/Tests/ODM/PHPCR/Functional/ReferrerTest.php
Expand Up @@ -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;
Expand All @@ -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;
Expand Down