From b8bcd51ff2ed8a00f56155cd14ae26e4f9fd4c0e Mon Sep 17 00:00:00 2001 From: romanb Date: Wed, 7 Oct 2009 12:39:46 +0000 Subject: [PATCH] [2.0][DDC-32] Fixed. --- lib/Doctrine/ORM/EntityManager.php | 9 +- lib/Doctrine/ORM/UnitOfWork.php | 28 +++++-- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 2 + .../ORM/Functional/BasicFunctionalTest.php | 82 +++++++++++++++++++ 4 files changed, 115 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 601be248d..753e675cb 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -312,12 +312,19 @@ class EntityManager */ public function getReference($entityName, $identifier) { + // Check identity map first, if its already in there just return it. + if ($entity = $this->_unitOfWork->tryGetById($identifier, + $this->_metadataFactory->getMetadataFor($entityName)->rootEntityName)) { + return $entity; + } + if ($this->_config->getAllowPartialObjects()) { $entity = new $entityName; - $this->getClassMetadata($entityName)->setEntityIdentifier($entity, $identifier); + $this->getClassMetadata($entityName)->setIdentifierValues($entity, $identifier); } else { $entity = $this->_proxyFactory->getReferenceProxy($entityName, $identifier); } + $this->_unitOfWork->registerManaged($entity, (array) $identifier, array()); return $entity; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 0613e0c96..eaa9d5028 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -24,6 +24,7 @@ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection, Doctrine\Common\Collections\Collection, Doctrine\Common\DoctrineException, + Doctrine\Common\NotifyPropertyChanged, Doctrine\Common\PropertyChangedListener, Doctrine\ORM\Event\LifecycleEventArgs; @@ -239,11 +240,19 @@ class UnitOfWork implements PropertyChangedListener * Commits the UnitOfWork, executing all operations that have been postponed * up to this point. The state of all managed entities will be synchronized with * the database. + * + * The operations are executed in the following order: + * + * 1) All entity insertions + * 2) All entity updates + * 3) All collection deletions + * 4) All collection updates + * 5) All entity deletions + * */ public function commit() { // Compute changes done since last commit. - // This populates _entityUpdates and _collectionUpdates. $this->computeChangeSets(); if ( ! ($this->_entityInsertions || @@ -879,6 +888,7 @@ class UnitOfWork implements PropertyChangedListener * Schedules an extra update that will be executed immediately after the * regular entity updates within the currently running commit cycle. * + * @ignore * @param $entity * @param $changeset */ @@ -959,6 +969,7 @@ class UnitOfWork implements PropertyChangedListener * Note that entities in a hierarchy are registered with the class name of * the root entity. * + * @ignore * @param object $entity The entity to register. * @return boolean TRUE if the registration was successful, FALSE if the identity of * the entity in question is already managed. @@ -968,14 +979,14 @@ class UnitOfWork implements PropertyChangedListener $classMetadata = $this->_em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->_entityIdentifiers[spl_object_hash($entity)]); if ($idHash === '') { - throw DoctrineException::entityMustHaveIdentifyToBeAddedToIdentityMap($entity); + throw DoctrineException::entityMustHaveIdentityToBeAddedToIdentityMap($entity); } $className = $classMetadata->rootEntityName; if (isset($this->_identityMap[$className][$idHash])) { return false; } $this->_identityMap[$className][$idHash] = $entity; - if ($entity instanceof \Doctrine\Common\NotifyPropertyChanged) { + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } return true; @@ -1021,6 +1032,7 @@ class UnitOfWork implements PropertyChangedListener * Removes an entity from the identity map. This effectively detaches the * entity from the persistence management of Doctrine. * + * @ignore * @param object $entity * @return boolean */ @@ -1046,6 +1058,7 @@ class UnitOfWork implements PropertyChangedListener * INTERNAL: * Gets an entity in the identity map by its identifier hash. * + * @ignore * @param string $idHash * @param string $rootClassName * @return object @@ -1060,6 +1073,7 @@ class UnitOfWork implements PropertyChangedListener * Tries to get an entity by its identifier hash. If no entity is found for * the given hash, FALSE is returned. * + * @ignore * @param string $idHash * @param string $rootClassName * @return mixed The found entity or FALSE. @@ -1097,6 +1111,7 @@ class UnitOfWork implements PropertyChangedListener * INTERNAL: * Checks whether an identifier hash exists in the identity map. * + * @ignore * @param string $idHash * @param string $rootClassName * @return boolean @@ -1597,6 +1612,7 @@ class UnitOfWork implements PropertyChangedListener * invoked on that entity at the beginning of the next commit of this * UnitOfWork. * + * @ignore * @param object $entity */ public function scheduleOrphanRemoval($entity) @@ -1636,6 +1652,7 @@ class UnitOfWork implements PropertyChangedListener * INTERNAL: * Creates an entity. Used for reconstitution of entities during hydration. * + * @ignore * @param string $className The name of the entity class. * @param array $data The data for the entity. * @return object The created entity instance. @@ -1668,7 +1685,7 @@ class UnitOfWork implements PropertyChangedListener $this->_entityStates[$oid] = self::STATE_MANAGED; $this->_originalEntityData[$oid] = $data; $this->_identityMap[$class->rootEntityName][$idHash] = $entity; - if ($entity instanceof \Doctrine\Common\NotifyPropertyChanged) { + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } $overrideLocalValues = true; @@ -1727,6 +1744,7 @@ class UnitOfWork implements PropertyChangedListener * INTERNAL: * Sets a property value of the original data array of an entity. * + * @ignore * @param string $oid * @param string $property * @param mixed $value @@ -1856,7 +1874,7 @@ class UnitOfWork implements PropertyChangedListener * @param array $id The identifier values. * @param array $data The original entity data. */ - public function registerManaged($entity, $id, $data) + public function registerManaged($entity, array $id, array $data) { $oid = spl_object_hash($entity); $this->_entityIdentifiers[$oid] = $id; diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 019a73f61..4d263615f 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -108,6 +108,8 @@ class CmsUser return false; } + public function getAddress() { return $this->address; } + public function setAddress(CmsAddress $address) { if ($this->address !== $address) { $this->address = $address; diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 8efa3ab99..91bdc5cbd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -349,4 +349,86 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->refresh($user); $this->assertEquals('developer', $user->status); } + + public function testAddToCollectionDoesNotInitialize() + { + $this->_em->getConfiguration()->setAllowPartialObjects(false); + + $user = new CmsUser; + $user->name = 'Guilherme'; + $user->username = 'gblanco'; + $user->status = 'developer'; + + for ($i=0; $i<3; ++$i) { + $phone = new CmsPhonenumber; + $phone->phonenumber = 100 + $i; + $user->addPhonenumber($phone); + } + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertEquals(3, $user->getPhonenumbers()->count()); + + $query = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u where u.username='gblanco'"); + + $gblanco = $query->getSingleResult(); + + $this->assertFalse($gblanco->getPhonenumbers()->isInitialized()); + + $newPhone = new CmsPhonenumber; + $phone->phonenumber = 555; + $gblanco->addPhonenumber($phone); + + $this->assertFalse($gblanco->getPhonenumbers()->isInitialized()); + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery("select u, p from Doctrine\Tests\Models\CMS\CmsUser u join u.phonenumbers p where u.username='gblanco'"); + $gblanco2 = $query->getSingleResult(); + $this->assertEquals(4, $gblanco2->getPhonenumbers()->count()); + + $this->_em->getConfiguration()->setAllowPartialObjects(true); + } + + public function testSetSetAssociationWithGetReference() + { + $user = new CmsUser; + $user->name = 'Guilherme'; + $user->username = 'gblanco'; + $user->status = 'developer'; + $this->_em->persist($user); + + $address = new CmsAddress; + $address->country = 'Germany'; + $address->city = 'Berlin'; + $address->zip = '12345'; + $this->_em->persist($address); + + $this->_em->flush(); + $this->_em->detach($address); + + $this->assertFalse($this->_em->contains($address)); + $this->assertTrue($this->_em->contains($user)); + + // Assume we only got the identifier of the address and now want to attach + // that address to the user without actually loading it, using getReference(). + $addressRef = $this->_em->getReference('Doctrine\Tests\Models\CMS\CmsAddress', $address->getId()); + + $user->setAddress($addressRef); + + $this->_em->flush(); + $this->_em->clear(); + + // Check with a fresh load that the association is indeed there + $query = $this->_em->createQuery("select u, a from Doctrine\Tests\Models\CMS\CmsUser u join u.address a where u.username='gblanco'"); + $gblanco = $query->getSingleResult(); + + $this->assertTrue($gblanco instanceof CmsUser); + $this->assertTrue($gblanco->getAddress() instanceof CmsAddress); + $this->assertEquals('Berlin', $gblanco->getAddress()->getCity()); + + } }