diff --git a/lib/Doctrine/ODM/PHPCR/DocumentManager.php b/lib/Doctrine/ODM/PHPCR/DocumentManager.php index 783f3b98e..6ad558a00 100644 --- a/lib/Doctrine/ODM/PHPCR/DocumentManager.php +++ b/lib/Doctrine/ODM/PHPCR/DocumentManager.php @@ -313,28 +313,33 @@ public function getClassMetadata($className) */ public function find($className, $id) { - 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 + $document = $this->unitOfWork->getDocumentById($id); + if ($document) { + $document = $this->documentHasValidClassName($document, $className) ? $document : null; + return $document; + } - $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; + } - 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; @@ -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) + { + try { + $this->unitOfWork->validateClassName($document, $className); + + return true; + } catch(ClassMismatchException $e) { + return false; + } + } + /** * Finds many documents by id. * @@ -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. + * + * @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.'); } - $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); + } - return $this->session->getNode($path); + return $node; } } diff --git a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php index badcacd9c..d69b5e796 100644 --- a/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php @@ -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 @@ -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. * @@ -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 @@ -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; } /** diff --git a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php index c66c069d3..05fdd5a8c 100644 --- a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php +++ b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php @@ -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 */ public function refreshDocumentForProxy($className, Proxy $document) { - $node = $this->session->getNode($this->determineDocumentId($document)); + $identifier = $this->determineDocumentId($document); + $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); @@ -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) @@ -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); @@ -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 { @@ -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); @@ -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']); @@ -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; @@ -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) { @@ -2953,7 +2957,7 @@ public function removeVersion($documentVersion) * * @param object $document */ - private function unregisterDocument($document) + public function unregisterDocument($document) { $oid = spl_object_hash($document); @@ -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(); @@ -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; } @@ -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); } private function setLocale($document, ClassMetadata $metadata, $locale) @@ -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; } /** diff --git a/tests/Doctrine/Tests/ODM/PHPCR/DocumentManagerTest.php b/tests/Doctrine/Tests/ODM/PHPCR/DocumentManagerTest.php index 5a2766c6d..bda2f8192 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/DocumentManagerTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/DocumentManagerTest.php @@ -2,8 +2,12 @@ namespace Doctrine\Tests\ODM\PHPCR; +use Doctrine\ODM\PHPCR\Configuration; use Doctrine\ODM\PHPCR\DocumentManager; use Doctrine\ODM\PHPCR\Mapping\ClassMetadata; +use PHPCR\ItemNotFoundException; +use PHPCR\Util\UUIDHelper; +use PHPCR\PathNotFoundException; /** * @group unit @@ -11,38 +15,56 @@ class DocumentManagerTest extends PHPCRTestCase { /** - * @covers Doctrine\ODM\PHPCR\DocumentManager::find - */ - public function testFind() - { - $fakeUuid = \PHPCR\Util\UUIDHelper::generateUUID(); - $session = $this->getMockForAbstractClass('PHPCR\SessionInterface', array('getNodeByIdentifier')); - $session->expects($this->once())->method('getNodeByIdentifier')->will($this->throwException(new \PHPCR\ItemNotFoundException(sprintf('403: %s', $fakeUuid)))); - $config = new \Doctrine\ODM\PHPCR\Configuration(); - - $dm = DocumentManager::create($session, $config); - $nonExistent = $dm->find(null, $fakeUuid); - - $this->assertNull($nonExistent); - } + * @covers Doctrine\ODM\PHPCR\DocumentManager::find + */ + public function testFindExceptionWhenUuidNotFound() + { + $fakeUuid = UUIDHelper::generateUUID(); + $session = $this->getMockForAbstractClass('PHPCR\SessionInterface', array('getNodeByIdentifier')); + $session->expects($this->once())->method('getNodeByIdentifier')->will($this->throwException(new ItemNotFoundException(sprintf('403: %s', $fakeUuid)))); + $config = new Configuration(); + + $dm = DocumentManager::create($session, $config); + + $nonExistent = $dm->find(null, $fakeUuid); + + $this->assertNull($nonExistent); + } /** - * @covers Doctrine\ODM\PHPCR\DocumentManager::findTranslation + * @covers Doctrine\ODM\PHPCR\DocumentManager::find */ - public function testFindTranslation() + public function testFindExceptionWhenIdNotFound() { - $fakeUuid = \PHPCR\Util\UUIDHelper::generateUUID(); - $session = $this->getMockForAbstractClass('PHPCR\SessionInterface', array('getNodeByIdentifier')); - $session->expects($this->once())->method('getNodeByIdentifier')->will($this->throwException(new \PHPCR\ItemNotFoundException(sprintf('403: %s', $fakeUuid)))); - $config = new \Doctrine\ODM\PHPCR\Configuration(); + $fakeId = 'fakeId'; + $session = $this->getMockForAbstractClass('PHPCR\SessionInterface', array('getNode')); + $session->expects($this->once())->method('getNode')->will($this->throwException(new PathNotFoundException(sprintf('403: %s', $fakeId)))); + $config = new Configuration(); $dm = DocumentManager::create($session, $config); - $nonExistent = $dm->findTranslation(null, $fakeUuid, 'en'); + $nonExistent = $dm->find(null, $fakeId); $this->assertNull($nonExistent); } + + /** + * @covers Doctrine\ODM\PHPCR\DocumentManager::findTranslation + */ + public function testFindTranslation() + { + $fakeUuid = UUIDHelper::generateUUID(); + $session = $this->getMockForAbstractClass('PHPCR\SessionInterface', array('getNodeByIdentifier')); + $session->expects($this->once())->method('getNodeByIdentifier')->will($this->throwException(new ItemNotFoundException(sprintf('403: %s', $fakeUuid)))); + $config = new Configuration(); + + $dm = DocumentManager::create($session, $config); + + $nonExistent = $dm->findTranslation(null, $fakeUuid, 'en'); + + $this->assertNull($nonExistent); + } /** * @covers Doctrine\ODM\PHPCR\DocumentManager::create @@ -51,7 +73,7 @@ public function testFindTranslation() public function testNewInstanceFromConfiguration() { $session = $this->getMock('PHPCR\SessionInterface'); - $config = new \Doctrine\ODM\PHPCR\Configuration(); + $config = new Configuration(); $dm = DocumentManager::create($session, $config); diff --git a/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php b/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php index cba2e966b..99d57b14e 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php @@ -9,6 +9,7 @@ use Jackalope\Factory; use Jackalope\Node; +use PHPCR\Util\UUIDHelper; /** * TODO: remove Jackalope dependency @@ -39,7 +40,6 @@ public function setUp() $this->factory = new Factory; $this->session = $this->getMock('Jackalope\Session', array(), array($this->factory), '', false); $this->objectManager = $this->getMock('Jackalope\ObjectManager', array(), array($this->factory), '', false); - $this->type = 'Doctrine\Tests\ODM\PHPCR\UoWUser'; $this->dm = DocumentManager::create($this->session); $this->uow = new UnitOfWork($this->dm); @@ -53,19 +53,22 @@ public function setUp() $cmf->setMetadataFor($this->type, $metadata); } - protected function createNode($id, $username) + protected function createNode($id, $username, $uuid = null) { $repository = $this->getMockBuilder('Jackalope\Repository')->disableOriginalConstructor()->getMock(); $this->session->expects($this->any()) ->method('getRepository') ->with() ->will($this->returnValue($repository)); - $nodeData = array( "jcr:primaryType" => "rep:root", "jcr:system" => array(), 'username' => $username, ); + + if (null !== $uuid) { + $nodeData['jcr:uuid'] = $uuid; + } return new Node($this->factory, $nodeData, $id, $this->session, $this->objectManager); } @@ -161,6 +164,27 @@ public function testUuid() $uow = new UnitOfWork($dm); $this->assertEquals('like-a-uuid', $method->invoke($uow)); } + + public function testGetOrCreateProxy() + { + $user = $this->uow->getOrCreateDocument($this->type, $this->createNode('/somepath', 'foo')); + $this->uow->clear(); + $userAsReference = $this->uow->getOrCreateProxy($user->id, get_class($user)); + + $this->assertEquals(2, $this->uow->getDocumentState($userAsReference)); + $this->assertEquals($userAsReference, $this->uow->getDocumentById($userAsReference->id)); + } + + public function testGetOrCreateProxyWithUuid() + { + $uuid = UUIDHelper::generateUUID(); + $user = $this->uow->getOrCreateDocument($this->type, $this->createNode('/somepath', 'foo', $uuid)); + $this->uow->clear(); + $userAsReference = $this->uow->getOrCreateProxy($uuid, get_class($user)); + + $this->assertEquals(2, $this->uow->getDocumentState($userAsReference)); + $this->assertEquals($userAsReference, $this->uow->getDocumentById($uuid)); + } } class UoWUser