Skip to content

allow to add uuid as id to proxies #493

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

Open
wants to merge 14 commits into
base: 2.0.x
Choose a base branch
from
Open
93 changes: 67 additions & 26 deletions lib/Doctrine/ODM/PHPCR/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -313,28 +313,33 @@ public function getClassMetadata($className)
*/
public function find($className, $id)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, the refacotring in this method is making things messier without really helping much. i would shove the whole code you have here below into a method cleanId($id), to prepare for further refactoring. but keep the order of things as before.

try {
if (UUIDHelper::isUUID($id)) {
try {
$id = $this->session->getNodeByIdentifier($id)->getPath();
} catch (ItemNotFoundException $e) {
return null;
}
} elseif (strpos($id, '/') !== 0) {
$id = '/'.$id;
}
// document still mapped -> return that when class name is valid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why "still"? i would move that inside the if and say "// document already mapped -> check class"

$document = $this->unitOfWork->getDocumentById($id);
if ($document) {
$document = $this->documentHasValidClassName($document, $className) ? $document : null;
return $document;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cs: needs a blank line before the return statement.

}

$document = $this->unitOfWork->getDocumentById($id);
if ($document) {
try {
$this->unitOfWork->validateClassName($document, $className);
// id can either be an path or an uuid, so we can have a look if one of both does have an entry
if (UUIDHelper::isUUID($id)) {
try {
$id = $this->session->getNodeByIdentifier($id)->getPath();
} catch (ItemNotFoundException $e) {
return null;
}
} elseif (strpos($id, '/') !== 0) {
$id = '/'.$id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what cases would the path not be absolute? Surely we want to throw an Exception here (or let the PHPCR\Session throw an exception for us)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same stuff as before, I just put the regquest for an existing document on top.
Decisions:

  • either document is still maped by uuid/id in the UoW
  • or id is a uuid -> fetch it from session by getNodeByIdentifier()
  • or id is paht -> fetch it from session by getNode()
    -> work on as befor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah now i understand your question. I just keep that ExceptionManagement cause i don't understand it at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dantleech reading the above removed code, it seems we already did the same before. not sure why but i would not change that behaviour in this refactoring. it should not break any BC.

}

return $document;
} catch (ClassMismatchException $e) {
return null;
}
// document mapped by id = absolute Path -> return that when class name is valid
$document = $this->unitOfWork->getDocumentById($id);
if ($document) {
$document = $this->documentHasValidClassName($document, $className) ? $document : null;
return $document;
}

}
// no document mapped, then try to fetch it from session and create a new one
try {
$node = $this->session->getNode($id);
} catch (PathNotFoundException $e) {
return null;
Expand All @@ -349,6 +354,25 @@ public function find($className, $id)
}
}

/**
* Just a little helper to validate the documents class name with a
* boolean answer.
*
* @param object $document
* @param string $className
* @return bool
*/
private function documentHasValidClassName($document, $className)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is not the right way to solve things. i would take out the actual logic form validateClassName into isValidClassName (in the UoW) that just returns a boolean. then you can call that directly. and valdiateClassName just calls if(!isValidClassName(...)) {throw...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 i just didn't like that long try-catch sections.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i totally agree with you, just think the solution should be to not even
throw an exception when not needed ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here i recognized a problem, some tests and some situations are waiting for some exceptions and i have got no clue when catch an exception and just return null (or some equal) or doesn't catch it. So the $session->getNode() is done in try-catch blocks and without in several places. I tried to catch them in the getNodeForDocument() for example but got lots of red tests by that. Here we need to get aware when implementing persisters too.

{
try {
$this->unitOfWork->validateClassName($document, $className);

return true;
} catch(ClassMismatchException $e) {
return false;
}
}

/**
* Finds many documents by id.
*
Expand Down Expand Up @@ -1190,23 +1214,40 @@ public function initializeObject($document)
}

/**
* Return the node of the given object
* Return the node of the given object or its hash.
*
* @param object $document
* This node is fetched by the objects uuid or its id means the absolute path.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't really get that.

*
* @param object|string $document
*
* @return \PHPCR\NodeInterface
*
* @throws InvalidArgumentException if $document is not an object.
* @throws InvalidArgumentException if $document isn't mapped in UoW.
* @throws PHPCRException if $document is not managed
*/
public function getNodeForDocument($document)
{
if (!is_object($document)) {
throw new InvalidArgumentException('Parameter $document needs to be an object, '.gettype($document).' given');
if (!$identifier = $this->unitOfWork->getDocumentId($document)) {
throw new InvalidArgumentException('Parameter document should have an entry in identityMap.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets say: "This document is not managed by this manager" or something like this.

}

$path = $this->unitOfWork->getDocumentId($document);
return $this->getNodeByPathOrUuid($identifier);
}

/**
* Creates a node from a given path or an uuid
*
* @param $pathOrUuid
* @return \PHPCR\NodeInterface
*/
public function getNodeByPathOrUuid($pathOrUuid)
{
if (UUIDHelper::isUUID($pathOrUuid)) {
$node = $this->session->getNodeByIdentifier($pathOrUuid);
} else {
$node = $this->session->getNode($pathOrUuid);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is wrong. its moving session dependency into the document manager, and duplicating getDocumentId. and it will only work if the document was already flushed, but not for new managed documents, as UoW::getDocumentId. we should move the logic to handle uuid into getDocumentId and make the uow look in the right places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean put that code into the getDocumentId(), ok would make sense.

}

return $this->session->getNode($path);
return $node;
}
}
20 changes: 14 additions & 6 deletions lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
use Doctrine\Common\Persistence\Mapping\ReflectionService;
use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
use Doctrine\Common\ClassLoader;
use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\InstantiatorInterface;

/**
* Metadata class
Expand Down Expand Up @@ -97,6 +95,13 @@ class ClassMetadata implements ClassMetadataInterface
*/
public $reflFields = array();

/**
* The prototype from which new instances of the mapped class are created.
*
* @var object
*/
private $prototype;

/**
* READ-ONLY: The ID generator used for generating IDs for this class.
*
Expand Down Expand Up @@ -1114,7 +1119,6 @@ public function hasField($fieldName)
return false;
}
return in_array($fieldName, $this->fieldMappings)
|| isset($this->inheritedFields[$fieldName])
|| $this->identifier === $fieldName
|| $this->localeMapping === $fieldName
|| $this->node === $fieldName
Expand Down Expand Up @@ -1464,11 +1468,15 @@ public function __sleep()
*/
public function newInstance()
{
if (!$this->instantiator instanceof InstantiatorInterface) {
$this->instantiator = new Instantiator();
if ($this->prototype === null) {
if (PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513) {
$this->prototype = $this->reflClass->newInstanceWithoutConstructor();
} else {
$this->prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
}
}

return $this->instantiator->instantiate($this->name);
return clone $this->prototype;
}

/**
Expand Down
59 changes: 25 additions & 34 deletions lib/Doctrine/ODM/PHPCR/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,17 @@ public function getOrCreateProxy($targetId, $className, $locale = null)
* Populate the proxy with actual data
*
* @param string $className
* @param Proxy $document
* @param Proxy $document
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, the CS says to align the parameters so it was correct before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't know why this hapend

*/
public function refreshDocumentForProxy($className, Proxy $document)
{
$node = $this->session->getNode($this->determineDocumentId($document));
$identifier = $this->determineDocumentId($document);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need to check if this will work on my working project, cause as the proxy document is registered, the id don't need to be determined. A mapping should exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so needed to roll back that dermineDocuemtnId($document) not shure why that document isn't mapped.

$node = $this->dm->getNodeByPathOrUuid($identifier);
if (UUIDHelper::isUUID($identifier)) {
// switch document registration to path as usual
$this->unregisterDocument($document);
$this->registerDocument($document, $node->getPath());
}

$hints = array('refresh' => true, 'fallback' => true);

Expand Down Expand Up @@ -1741,13 +1747,11 @@ private function doRefresh($document, &$visited)
throw new InvalidArgumentException('Document has to be managed to be refreshed '.self::objToStr($document, $this->dm));
}

$node = $this->session->getNode($this->getDocumentId($document));

$class = $this->dm->getClassMetadata(get_class($document));
$this->cascadeRefresh($class, $document, $visited);

$hints = array('refresh' => true);
$this->getOrCreateDocument(ClassUtils::getClass($document), $node, $hints);
$this->getOrCreateDocument(ClassUtils::getClass($document), $this->dm->getNodeForDocument($document), $hints);
}

public function merge($document)
Expand Down Expand Up @@ -2402,7 +2406,7 @@ private function executeUpdates($documents, $dispatchEvents = true)
}

$class = $this->dm->getClassMetadata(get_class($document));
$node = $this->session->getNode($this->getDocumentId($document));
$node = $this->dm->getNodeForDocument($document);

if ($this->writeMetadata) {
$this->documentClassMapper->writeMetadata($this->dm, $node, $class->name);
Expand Down Expand Up @@ -2491,7 +2495,7 @@ private function executeUpdates($documents, $dispatchEvents = true)
continue;
}

$associatedNode = $this->session->getNode($this->getDocumentId($fv));
$associatedNode = $this->dm->getNodeForDocument($fv);
if ($strategy === PropertyType::PATH) {
$refNodesIds[] = $associatedNode->getPath();
} else {
Expand All @@ -2509,7 +2513,7 @@ private function executeUpdates($documents, $dispatchEvents = true)
}
} elseif ($mapping['type'] === $class::MANY_TO_ONE) {
if (isset($fieldValue)) {
$associatedNode = $this->session->getNode($this->getDocumentId($fieldValue));
$associatedNode = $this->dm->getNodeForDocument($fieldValue);

if ($strategy === PropertyType::PATH) {
$node->setProperty($fieldName, $associatedNode->getPath(), $strategy);
Expand Down Expand Up @@ -2541,7 +2545,7 @@ private function executeUpdates($documents, $dispatchEvents = true)
throw new PHPCRException(sprintf("%s is not an instance of %s for document %s field %s", self::objToStr($fv, $this->dm), $mapping['referencedBy'], self::objToStr($document, $this->dm), $mapping['fieldName']));
}

$referencingNode = $this->session->getNode($this->getDocumentId($fv));
$referencingNode = $this->dm->getNodeForDocument($fv);
$referencingMeta = $this->dm->getClassMetadata($mapping['referringDocument']);
$referencingField = $referencingMeta->getAssociation($mapping['referencedBy']);

Expand Down Expand Up @@ -2724,7 +2728,7 @@ private function executeReorders($documents)
}
foreach ($list as $value) {
list($parent, $src, $target, $before) = $value;
$parentNode = $this->session->getNode($this->getDocumentId($parent));
$parentNode = $this->dm->getNodeForDocument($parent);

// check for src and target ...
$dest = $target;
Expand Down Expand Up @@ -2772,7 +2776,7 @@ private function executeRemovals($documents)
$id = $this->getDocumentId($document);

try {
$node = $this->session->getNode($id);
$node = $this->dm->getNodeByPathOrUuid($id);
$this->doRemoveAllTranslations($document, $class);
$node->remove();
} catch (PathNotFoundException $e) {
Expand Down Expand Up @@ -2953,7 +2957,7 @@ public function removeVersion($documentVersion)
*
* @param object $document
*/
private function unregisterDocument($document)
public function unregisterDocument($document)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can stay private

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want to not manage a document anymore through the API, use $dm->detach($document)

{
$oid = spl_object_hash($document);

Expand Down Expand Up @@ -3138,7 +3142,7 @@ public function getLocalesFor($document)
$oid = spl_object_hash($document);
if ($this->contains($oid)) {
try {
$node = $this->session->getNode($this->getDocumentId($document));
$node = $this->dm->getNodeForDocument($document);
$locales = $this->dm->getTranslationStrategy($metadata->translator)->getLocalesFor($document, $node, $metadata);
} catch (PathNotFoundException $e) {
$locales = array();
Expand Down Expand Up @@ -3264,7 +3268,7 @@ protected function doLoadDatabaseTranslation($document, ClassMetadata $metadata,

$strategy = $this->dm->getTranslationStrategy($metadata->translator);
try {
$node = $this->session->getNode($this->getDocumentId($oid));
$node = $this->dm->getNodeForDocument($oid);
if ($strategy->loadTranslation($document, $node, $metadata, $locale)) {
return $locale;
}
Expand Down Expand Up @@ -3464,9 +3468,8 @@ private function doRemoveAllTranslations($document, ClassMetadata $metadata)
return;
}

$node = $this->session->getNode($this->getDocumentId($document));
$strategy = $this->dm->getTranslationStrategy($metadata->translator);
$strategy->removeAllTranslations($document, $node, $metadata);
$strategy->removeAllTranslations($document, $this->dm->getNodeForDocument($document), $metadata);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, lets keep the node assignment above.

}

private function setLocale($document, ClassMetadata $metadata, $locale)
Expand Down Expand Up @@ -3573,28 +3576,16 @@ private static function objToStr($obj, DocumentManager $dm = null)

private function getVersionedNodePath($document)
{
$path = $this->getDocumentId($document);
$id = $this->getDocumentId($document);
$metadata = $this->dm->getClassMetadata(get_class($document));

if (!$metadata->versionable) {
throw new InvalidArgumentException(sprintf(
"The document at path '%s' is not versionable",
$path
));
}

$node = $this->session->getNode($path);

$mixin = $metadata->versionable === 'simple' ?
'mix:simpleVersionable' :
'mix:versionable';

if (!$node->isNodeType($mixin)) {
$node->addMixin($mixin);
if ($metadata->versionable !== 'full') {
throw new InvalidArgumentException(sprintf("The document at '%s' is not full versionable", $id));
}

$node = $this->dm->getNodeByPathOrUuid($id);
$node->addMixin('mix:versionable');

return $path;
return $id;
}

/**
Expand Down
Loading