From ae1b9371ecdec8de5df3c0ac7aed82fc3f863351 Mon Sep 17 00:00:00 2001 From: romanb Date: Tue, 21 Jul 2009 15:53:58 +0000 Subject: [PATCH] [2.0] Fixed #2366. --- lib/Doctrine/ORM/EntityManager.php | 5 +- .../ORM/Internal/Hydration/ObjectHydrator.php | 2 +- .../ORM/Mapping/AssociationMapping.php | 24 +++---- lib/Doctrine/ORM/PersistentCollection.php | 25 ++++--- .../Persisters/ElementCollectionPersister.php | 1 + .../ORM/Persisters/ManyToManyPersister.php | 2 +- .../ORM/Persisters/OneToManyPersister.php | 56 ++++++++++++++-- .../Persisters/StandardEntityPersister.php | 22 ++---- lib/Doctrine/ORM/UnitOfWork.php | 67 ++++++++++++++++++- .../ORM/Functional/BasicFunctionalTest.php | 50 +++++++++++++- .../OneToOneBidirectionalAssociationTest.php | 2 +- 11 files changed, 203 insertions(+), 53 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index b17fc687c..c77ffdcbb 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -413,13 +413,12 @@ class EntityManager * Refreshes the persistent state of an entity from the database, * overriding any local changes that have not yet been persisted. * - * @param object $entity - * @todo Implementation + * @param object $entity The entity to refresh. */ public function refresh($entity) { $this->_errorIfClosed(); - throw DoctrineException::notImplemented(); + $this->_unitOfWork->refresh($entity); } /** diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 14150d0bf..7dd9aa086 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -260,7 +260,7 @@ class ObjectHydrator extends AbstractHydrator } else { // Eager load //TODO: Allow more efficient and configurable batching of these loads - $assoc->load($entity, new $className, $this->_em, $joinColumns); + $assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumns); } } else { //TODO: Eager load diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index 963ca9ba1..cd72b09c3 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -37,8 +37,18 @@ namespace Doctrine\ORM\Mapping; */ abstract class AssociationMapping { - const FETCH_MANUAL = 1; + /** + * Specifies that an association is to be fetched when it is first accessed. + * + * @var integer + */ const FETCH_LAZY = 2; + /** + * Specifies that an association is to be fetched when the owner of the + * association is fetched. + * + * @var integer + */ const FETCH_EAGER = 3; /** @@ -66,7 +76,7 @@ abstract class AssociationMapping * * @var integer */ - public $fetchMode = self::FETCH_MANUAL; + public $fetchMode = self::FETCH_LAZY; /** * Flag that indicates whether the class that defines this mapping is @@ -244,16 +254,6 @@ abstract class AssociationMapping return $this->fetchMode == self::FETCH_LAZY; } - /** - * Whether the target entity/entities of the association are manually fetched. - * - * @return boolean - */ - public function isManuallyFetched() - { - return $this->fetchMode == self::FETCH_MANUAL; - } - /** * Whether the source entity of this association represents the owning side. * diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 927d81e65..2b9a7ef6e 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -198,7 +198,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection public function remove($key) { //TODO: delete entity if shouldDeleteOrphans - /*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans()) { + /*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans) { $this->_em->remove($removed); }*/ $removed = parent::remove($key); @@ -209,8 +209,8 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection } /** - * When the collection is a Map this is like put(key,value)/add(key,value). - * When the collection is a List this is like add(position,value). + * When the collection is used as a Map this is like put(key,value)/add(key,value). + * When the collection is used as a List this is like add(position,value). * * @param integer $key * @param mixed $value @@ -301,10 +301,13 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection { } - + + /** + * Initializes the collection by loading its contents from the database. + */ private function _initialize() { - + } /** @@ -375,15 +378,21 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection { //TODO: Register collection as dirty with the UoW if necessary //TODO: If oneToMany() && shouldDeleteOrphan() delete entities - /*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans()) { + /*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans) { foreach ($this->_data as $entity) { $this->_em->remove($entity); } }*/ parent::clear(); - $this->_changed(); + if ($this->_association->isOwningSide) { + $this->_changed(); + $this->_em->getUnitOfWork()->scheduleCollectionDeletion($this); + } } - + + /** + * Marks this collection as changed/dirty. + */ private function _changed() { $this->_isDirty = true; diff --git a/lib/Doctrine/ORM/Persisters/ElementCollectionPersister.php b/lib/Doctrine/ORM/Persisters/ElementCollectionPersister.php index bf47f9851..67ae40081 100644 --- a/lib/Doctrine/ORM/Persisters/ElementCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/ElementCollectionPersister.php @@ -25,6 +25,7 @@ namespace Doctrine\ORM\Persisters; * Persister for collections of basic elements / value types. * * @author robo + * @todo Implementation once support for collections of basic elements (i.e. strings) is added. */ class ElementCollectionPersister extends AbstractCollectionPersister { diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index cc237ce1b..60a143921 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -108,7 +108,7 @@ class ManyToManyPersister extends AbstractCollectionPersister $mapping = $coll->getMapping(); $joinTable = $mapping->getJoinTable(); $whereClause = ''; - foreach ($mapping->getSourceToRelationKeyColumns() as $relationColumn) { + foreach ($mapping->sourceToRelationKeyColumns as $relationColumn) { if ($whereClause !== '') $whereClause .= ' AND '; $whereClause .= "$relationColumn = ?"; } diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index 2ab99e9fb..a4bf267de 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -26,18 +26,23 @@ use Doctrine\ORM\PersistentCollection; /** * Persister for one-to-many collections. * - * This persister is only used for uni-directional one-to-many mappings. + * IMPORTANT: + * This persister is only used for uni-directional one-to-many mappings on a foreign key + * (which are not yet supported). So currently this persister is not used. * * @since 2.0 * @author Roman Borschel + * @todo Complete implementation when the support for uni-directional one-to-many mappings + * on a foreign key gets added. */ class OneToManyPersister extends AbstractCollectionPersister { /** - * {@inheritdoc} + * Generates the SQL UPDATE that updates a particular row's foreign + * key to null. * - * @param $coll - * @return + * @param PersistentCollection $coll + * @return string * @override */ protected function _getDeleteRowSql(PersistentCollection $coll) @@ -63,14 +68,53 @@ class OneToManyPersister extends AbstractCollectionPersister return array("UPDATE $table SET $setClause WHERE $whereClause", $this->_uow->getEntityIdentifier($element)); } - protected function _getInsertRowSql() + protected function _getInsertRowSql(PersistentCollection $coll) { return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz"; } /* Not used for OneToManyPersister */ - protected function _getUpdateRowSql() + protected function _getUpdateRowSql(PersistentCollection $coll) { return; } + + /** + * Generates the SQL UPDATE that updates all the foreign keys to null. + * + * @param PersistentCollection $coll + */ + protected function _getDeleteSql(PersistentCollection $coll) + { + + } + + /** + * Gets the SQL parameters for the corresponding SQL statement to delete + * the given collection. + * + * @param PersistentCollection $coll + */ + protected function _getDeleteSqlParameters(PersistentCollection $coll) + {} + + /** + * Gets the SQL parameters for the corresponding SQL statement to insert the given + * element of the given collection into the database. + * + * @param PersistentCollection $coll + * @param mixed $element + */ + protected function _getInsertRowSqlParameters(PersistentCollection $coll, $element) + {} + + /** + * Gets the SQL parameters for the corresponding SQL statement to delete the given + * element from the given collection. + * + * @param PersistentCollection $coll + * @param mixed $element + */ + protected function _getDeleteRowSqlParameters(PersistentCollection $coll, $element) + {} } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index f0aa6d911..b3d418cef 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -70,13 +70,6 @@ class StandardEntityPersister */ protected $_em; - /** - * The EventManager instance. - * - * @var Doctrine\Common\EventManager - */ - protected $_evm; - /** * Queued inserts. * @@ -96,7 +89,6 @@ class StandardEntityPersister { $this->_em = $em; $this->_platform = $em->getConnection()->getDatabasePlatform(); - $this->_evm = $em->getEventManager(); $this->_entityName = $class->name; $this->_conn = $em->getConnection(); $this->_class = $class; @@ -206,17 +198,9 @@ class StandardEntityPersister ); $tableName = $this->_class->primaryTable['name']; - if ($this->_evm->hasListeners(Events::preUpdate)) { - $this->_preUpdate($entity); - } - if (isset($updateData[$tableName]) && $updateData[$tableName]) { $this->_doUpdate($entity, $tableName, $updateData[$tableName], $id); } - - if ($this->_evm->hasListeners(Events::postUpdate)) { - $this->_postUpdate($entity); - } } /** @@ -425,7 +409,7 @@ class StandardEntityPersister if (isset($this->_class->fieldNames[$column])) { $fieldName = $this->_class->fieldNames[$column]; $data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName)) - ->convertToPHPValue($value, $this->_platform); + ->convertToPHPValue($value, $this->_platform); } else { $joinColumnValues[$column] = $value; } @@ -458,7 +442,9 @@ class StandardEntityPersister $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues); $this->_class->reflFields[$field]->setValue($entity, $proxy); } else { - //TODO: Eager fetch + // Eager load + //TODO: Allow more efficient and configurable batching of these loads + $assoc->load($entity, new $assoc->targetEntityName, $this->_em, $joinColumnValues); } } else { // Inject collection diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index d55cb0f70..dc3ed4b3c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1309,6 +1309,71 @@ class UnitOfWork implements PropertyChangedListener return $managedCopy; } + + /** + * + * + * @param $entity + * @return unknown_type + */ + public function refresh($entity) + { + $visited = array(); + return $this->_doRefresh($entity, $visited); + } + + /** + * Executes a refresh operation on an entity. + * + * @param object $entity The entity to refresh. + * @param array $visited The already visited entities during cascades. + */ + private function _doRefresh($entity, array &$visited) + { + $oid = spl_object_hash($entity); + if (isset($visited[$oid])) { + return; // Prevent infinite recursion + } + + $visited[$oid] = $entity; // mark visited + + $class = $this->_em->getClassMetadata(get_class($entity)); + switch ($this->getEntityState($entity)) { + case self::STATE_MANAGED: + $this->getEntityPersister($class->name)->load( + array_combine($class->identifier, $this->_entityIdentifiers[$oid]), + $entity + ); + break; + default: + throw DoctrineException::updateMe("NEW, REMOVED or DETACHED entity can not be refreshed."); + } + $this->_cascadeRefresh($entity, $visited); + } + + /** + * Cascades a refresh operation to associated entities. + * + * @param object $entity + * @param array $visited + */ + private function _cascadeRefresh($entity, array &$visited) + { + $class = $this->_em->getClassMetadata(get_class($entity)); + foreach ($class->associationMappings as $assocMapping) { + if ( ! $assocMapping->isCascadeRefresh) { + continue; + } + $relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]->getValue($entity); + if ($relatedEntities instanceof Collection) { + foreach ($relatedEntities as $relatedEntity) { + $this->_doRefresh($relatedEntity, $visited); + } + } else if ($relatedEntities !== null) { + $this->_doRefresh($relatedEntities, $visited); + } + } + } /** * Cascades a merge operation to associated entities. @@ -1324,7 +1389,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! $assocMapping->isCascadeMerge) { continue; } - $relatedEntities = $class->reflFields[$assocMapping->getSourceFieldName()] + $relatedEntities = $class->reflFields[$assocMapping->sourceFieldName] ->getValue($entity); if ($relatedEntities instanceof Collection) { foreach ($relatedEntities as $relatedEntity) { diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 136a2647b..19444040d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -173,8 +173,8 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase array())->fetchColumn(); $this->assertEquals(10, $count); - //$user->groups->clear(); - unset($user->groups); + $user->groups->clear(); + //unset($user->groups); $this->_em->flush(); @@ -183,6 +183,35 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase array())->fetchColumn(); $this->assertEquals(0, $count); } + + /* NOT YET IMPLEMENTED + public function testOneToManyOrphanDelete() + { + $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(); + + $user->getPhonenumbers()->remove(0); + + $this->_em->flush(); + + // Check that the links in the association table have been deleted + $count = $this->_em->getConnection()->execute("SELECT COUNT(*) FROM cms_phonenumbers", + array())->fetchColumn(); + $this->assertEquals(2, $count); // only 2 remaining + + }*/ public function testBasicQuery() { @@ -304,4 +333,21 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u inner join u.groups g"); $this->assertEquals(0, count($query->getResultList())); } + + public function testBasicRefresh() + { + $user = new CmsUser; + $user->name = 'Guilherme'; + $user->username = 'gblanco'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush(); + + $user->status = 'mascot'; + + $this->assertEquals('mascot', $user->status); + $this->_em->refresh($user); + $this->assertEquals('developer', $user->status); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php index df57f8d34..7d4aa6111 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php @@ -83,7 +83,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional $this->_createFixture(); $this->_em->getConfiguration()->setAllowPartialObjects(false); $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer'); - $metadata->getAssociationMapping('cart')->fetchMode = AssociationMapping::FETCH_LAZY; + $metadata->getAssociationMapping('mentor')->fetchMode = AssociationMapping::FETCH_EAGER; $query = $this->_em->createQuery('select c from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c'); $result = $query->getResultList();