1
0
mirror of synced 2025-02-06 23:39:25 +03:00

[2.0] Fixed #2373. Some small perf. improvements for UnitOfWork.

This commit is contained in:
romanb 2009-07-23 09:52:16 +00:00
parent b121576ff4
commit d674f1923d
9 changed files with 91 additions and 51 deletions

View File

@ -184,11 +184,13 @@ class AnnotationDriver implements Driver
$mapping['joinColumns'] = $joinColumns; $mapping['joinColumns'] = $joinColumns;
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy; $mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
$mapping['cascade'] = $oneToOneAnnot->cascade; $mapping['cascade'] = $oneToOneAnnot->cascade;
$mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
$metadata->mapOneToOne($mapping); $metadata->mapOneToOne($mapping);
} else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) { } else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) {
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy; $mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
$mapping['targetEntity'] = $oneToManyAnnot->targetEntity; $mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
$mapping['cascade'] = $oneToManyAnnot->cascade; $mapping['cascade'] = $oneToManyAnnot->cascade;
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
$metadata->mapOneToMany($mapping); $metadata->mapOneToMany($mapping);
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) { } else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
$mapping['joinColumns'] = $joinColumns; $mapping['joinColumns'] = $joinColumns;

View File

@ -65,12 +65,14 @@ final class OneToOne extends \Doctrine\Common\Annotations\Annotation {
public $cascade; public $cascade;
public $fetch; public $fetch;
public $optional; public $optional;
public $orphanRemoval = false;
} }
final class OneToMany extends \Doctrine\Common\Annotations\Annotation { final class OneToMany extends \Doctrine\Common\Annotations\Annotation {
public $mappedBy; public $mappedBy;
public $targetEntity; public $targetEntity;
public $cascade; public $cascade;
public $fetch; public $fetch;
public $orphanRemoval = false;
} }
final class ManyToOne extends \Doctrine\Common\Annotations\Annotation { final class ManyToOne extends \Doctrine\Common\Annotations\Annotation {
public $targetEntity; public $targetEntity;

View File

@ -43,7 +43,7 @@ namespace Doctrine\ORM\Mapping;
class OneToManyMapping extends AssociationMapping class OneToManyMapping extends AssociationMapping
{ {
/** Whether to delete orphaned elements (removed from the collection) */ /** Whether to delete orphaned elements (removed from the collection) */
public $deleteOrphans = false; public $orphanRemoval = false;
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */ /** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
//public $keyColumn; //public $keyColumn;
@ -73,8 +73,8 @@ class OneToManyMapping extends AssociationMapping
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']); throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
} }
$this->deleteOrphans = isset($mapping['deleteOrphans']) ? $this->orphanRemoval = isset($mapping['orphanRemoval']) ?
(bool)$mapping['deleteOrphans'] : false; (bool) $mapping['orphanRemoval'] : false;
} }
/** /**

View File

@ -58,7 +58,7 @@ class OneToOneMapping extends AssociationMapping
* *
* @var boolean * @var boolean
*/ */
public $deleteOrphans = false; public $orphanRemoval = false;
/** /**
* The join column definitions. * The join column definitions.
@ -107,8 +107,8 @@ class OneToOneMapping extends AssociationMapping
$this->targetToSourceKeyColumns = array_flip($this->sourceToTargetKeyColumns); $this->targetToSourceKeyColumns = array_flip($this->sourceToTargetKeyColumns);
} }
$this->deleteOrphans = isset($mapping['deleteOrphans']) ? $this->orphanRemoval = isset($mapping['orphanRemoval']) ?
(bool)$mapping['deleteOrphans'] : false; (bool) $mapping['orphanRemoval'] : false;
return $mapping; return $mapping;
} }

View File

@ -197,14 +197,14 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
*/ */
public function remove($key) public function remove($key)
{ {
//TODO: delete entity if shouldDeleteOrphans
/*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans) {
$this->_em->remove($removed);
}*/
$removed = parent::remove($key); $removed = parent::remove($key);
if ($removed) { if ($removed) {
$this->_changed(); $this->_changed();
if ($this->_association->isOneToMany() && $this->_association->orphanRemoval) {
$this->_em->getUnitOfWork()->scheduleOrphanRemoval($removed);
} }
}
return $removed; return $removed;
} }

View File

@ -231,6 +231,13 @@ class UnitOfWork implements PropertyChangedListener
*/ */
private $_evm; private $_evm;
/**
* Orphaned entities scheduled for removal.
*
* @var array
*/
private $_orphanRemovals = array();
/** /**
* Initializes a new UnitOfWork instance, bound to the given EntityManager. * Initializes a new UnitOfWork instance, bound to the given EntityManager.
* *
@ -254,17 +261,24 @@ class UnitOfWork implements PropertyChangedListener
// This populates _entityUpdates and _collectionUpdates. // This populates _entityUpdates and _collectionUpdates.
$this->computeChangeSets(); $this->computeChangeSets();
if (empty($this->_entityInsertions) && if ( ! ($this->_entityInsertions ||
empty($this->_entityDeletions) && $this->_entityDeletions ||
empty($this->_entityUpdates) && $this->_entityUpdates ||
empty($this->_collectionUpdates) && $this->_collectionUpdates ||
empty($this->_collectionDeletions)) { $this->_collectionDeletions ||
$this->_orphanRemovals)) {
return; // Nothing to do. return; // Nothing to do.
} }
// Now we need a commit order to maintain referential integrity // Now we need a commit order to maintain referential integrity
$commitOrder = $this->_getCommitOrder(); $commitOrder = $this->_getCommitOrder();
if ($this->_orphanRemovals) {
foreach ($this->_orphanRemovals as $orphan) {
$this->remove($orphan);
}
}
$conn = $this->_em->getConnection(); $conn = $this->_em->getConnection();
try { try {
$conn->beginTransaction(); $conn->beginTransaction();
@ -317,17 +331,18 @@ class UnitOfWork implements PropertyChangedListener
} }
// Clear up // Clear up
$this->_entityInsertions = array(); $this->_entityInsertions =
$this->_entityUpdates = array(); $this->_entityUpdates =
$this->_entityDeletions = array(); $this->_entityDeletions =
$this->_extraUpdates = array(); $this->_extraUpdates =
$this->_entityChangeSets = array(); $this->_entityChangeSets =
$this->_collectionUpdates = array(); $this->_collectionUpdates =
$this->_collectionDeletions = array(); $this->_collectionDeletions =
$this->_visitedCollections = array(); $this->_visitedCollections =
$this->_orphanRemovals = array();
} }
protected function _executeExtraUpdates() private function _executeExtraUpdates()
{ {
foreach ($this->_extraUpdates as $oid => $update) { foreach ($this->_extraUpdates as $oid => $update) {
list ($entity, $changeset) = $update; list ($entity, $changeset) = $update;
@ -363,11 +378,8 @@ class UnitOfWork implements PropertyChangedListener
*/ */
public function computeChangeSets() public function computeChangeSets()
{ {
$entitySet = $this->_identityMap;
$entityInsertions = $this->_entityInsertions;
// Compute changes for INSERTed entities first. This must always happen. // Compute changes for INSERTed entities first. This must always happen.
foreach ($entityInsertions as $entity) { foreach ($this->_entityInsertions as $entity) {
$class = $this->_em->getClassMetadata(get_class($entity)); $class = $this->_em->getClassMetadata(get_class($entity));
$this->_computeEntityChanges($class, $entity); $this->_computeEntityChanges($class, $entity);
// Look for changes in associations of the entity // Look for changes in associations of the entity
@ -380,7 +392,7 @@ class UnitOfWork implements PropertyChangedListener
} }
// Compute changes for other MANAGED entities. Change tracking policies take effect here. // Compute changes for other MANAGED entities. Change tracking policies take effect here.
foreach ($entitySet as $className => $entities) { foreach ($this->_identityMap as $className => $entities) {
$class = $this->_em->getClassMetadata($className); $class = $this->_em->getClassMetadata($className);
// Skip class if change tracking happens through notification // Skip class if change tracking happens through notification
@ -493,8 +505,13 @@ class UnitOfWork implements PropertyChangedListener
if (isset($changeSet[$propName])) { if (isset($changeSet[$propName])) {
if (isset($class->associationMappings[$propName])) { if (isset($class->associationMappings[$propName])) {
$assoc = $class->associationMappings[$propName]; $assoc = $class->associationMappings[$propName];
if ($assoc->isOneToOne() && $assoc->isOwningSide) { if ($assoc->isOneToOne()) {
if ($assoc->isOwningSide) {
$entityIsDirty = true; $entityIsDirty = true;
}
if ($actualValue === null && $assoc->orphanRemoval) {
$this->scheduleOrphanRemoval($orgValue);
}
} else if ($orgValue instanceof PersistentCollection) { } else if ($orgValue instanceof PersistentCollection) {
// A PersistentCollection was de-referenced, so delete it. // A PersistentCollection was de-referenced, so delete it.
if ( ! in_array($orgValue, $this->_collectionDeletions, true)) { if ( ! in_array($orgValue, $this->_collectionDeletions, true)) {
@ -1478,19 +1495,39 @@ class UnitOfWork implements PropertyChangedListener
$this->_collectionDeletions = array(); $this->_collectionDeletions = array();
//$this->_collectionCreations = array(); //$this->_collectionCreations = array();
$this->_collectionUpdates = array(); $this->_collectionUpdates = array();
//$this->_orphanRemovals = array();
$this->_commitOrderCalculator->clear(); $this->_commitOrderCalculator->clear();
} }
public function scheduleCollectionUpdate(PersistentCollection $coll) /**
* INTERNAL:
* Schedules an orphaned entity for removal. The remove() operation will be
* invoked on that entity at the beginning of the next commit of this
* UnitOfWork.
*
* @param object $entity
*/
public function scheduleOrphanRemoval($entity)
{
$this->_orphanRemovals[spl_object_hash($entity)] = $entity;
}
/*public function scheduleCollectionUpdate(PersistentCollection $coll)
{ {
$this->_collectionUpdates[] = $coll; $this->_collectionUpdates[] = $coll;
} }*/
public function isCollectionScheduledForUpdate(PersistentCollection $coll) /*public function isCollectionScheduledForUpdate(PersistentCollection $coll)
{ {
//... //...
} }*/
/**
* INTERNAL:
* Schedules a complete collection for removal when this UnitOfWork commits.
*
* @param PersistentCollection $coll
*/
public function scheduleCollectionDeletion(PersistentCollection $coll) public function scheduleCollectionDeletion(PersistentCollection $coll)
{ {
//TODO: if $coll is already scheduled for recreation ... what to do? //TODO: if $coll is already scheduled for recreation ... what to do?
@ -1503,15 +1540,15 @@ class UnitOfWork implements PropertyChangedListener
//... //...
} }
public function scheduleCollectionRecreation(PersistentCollection $coll) /*public function scheduleCollectionRecreation(PersistentCollection $coll)
{ {
$this->_collectionRecreations[] = $coll; $this->_collectionRecreations[] = $coll;
} }*/
public function isCollectionScheduledForRecreation(PersistentCollection $coll) /*public function isCollectionScheduledForRecreation(PersistentCollection $coll)
{ {
//... //...
} }*/
/** /**
* INTERNAL: * INTERNAL:
@ -1519,8 +1556,8 @@ class UnitOfWork implements PropertyChangedListener
* *
* @param string $className The name of the entity class. * @param string $className The name of the entity class.
* @param array $data The data for the entity. * @param array $data The data for the entity.
* @return object * @return object The created entity instance.
* @internal Performance-sensitive method. Run the performance test suites when * @internal Highly performance-sensitive method. Run the performance test suites when
* making modifications. * making modifications.
*/ */
public function createEntity($className, array $data, $hints = array()) public function createEntity($className, array $data, $hints = array())
@ -1783,7 +1820,7 @@ class UnitOfWork implements PropertyChangedListener
*/ */
public function propertyChanged($entity, $propertyName, $oldValue, $newValue) public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
{ {
//if ($this->getEntityState($entity) == self::STATE_MANAGED) { if ($this->getEntityState($entity) == self::STATE_MANAGED) {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$class = $this->_em->getClassMetadata(get_class($entity)); $class = $this->_em->getClassMetadata(get_class($entity));
@ -1802,6 +1839,6 @@ class UnitOfWork implements PropertyChangedListener
} else { } else {
$this->_entityUpdates[$oid] = $entity; $this->_entityUpdates[$oid] = $entity;
} }
//} }
} }
} }

View File

@ -26,7 +26,7 @@ class CmsUser
*/ */
public $name; public $name;
/** /**
* @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"save", "delete"}) * @OneToMany(targetEntity="CmsPhonenumber", mappedBy="user", cascade={"save", "delete"}, orphanRemoval=true)
*/ */
public $phonenumbers; public $phonenumbers;
/** /**

View File

@ -184,8 +184,7 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, $count); $this->assertEquals(0, $count);
} }
/* NOT YET IMPLEMENTED public function testOneToManyOrphanRemoval()
public function testOneToManyOrphanDelete()
{ {
$user = new CmsUser; $user = new CmsUser;
$user->name = 'Guilherme'; $user->name = 'Guilherme';
@ -203,15 +202,15 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
$user->getPhonenumbers()->remove(0); $user->getPhonenumbers()->remove(0);
$this->assertEquals(2, count($user->getPhonenumbers()));
$this->_em->flush(); $this->_em->flush();
// Check that the links in the association table have been deleted // Check that there are just 2 phonenumbers left
$count = $this->_em->getConnection()->execute("SELECT COUNT(*) FROM cms_phonenumbers", $count = $this->_em->getConnection()->execute("SELECT COUNT(*) FROM cms_phonenumbers",
array())->fetchColumn(); array())->fetchColumn();
$this->assertEquals(2, $count); // only 2 remaining $this->assertEquals(2, $count); // only 2 remaining
}
}*/
public function testBasicQuery() public function testBasicQuery()
{ {