Skip to content

Commit

Permalink
Single entity flush test
Browse files Browse the repository at this point in the history
  • Loading branch information
goetas committed Sep 27, 2016
1 parent cd11723 commit fda6ff3
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 26 deletions.
74 changes: 54 additions & 20 deletions lib/Doctrine/ORM/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ class UnitOfWork implements PropertyChangedListener
*/
private $entityInsertions = array();

/**
* A list of all pending entity insertions, that can be avoided if we call
* a flush() method with a non related entity.
* @var array
*/
private $entityShouldBeInserted = array();

/**
* Used to detect if a flush is in progress.
* @see UnitOfWork::scheduleForInsert() method.
* @var boolean
*/
private $flushing = false;


/**
* A list of all pending entity updates.
*
Expand Down Expand Up @@ -321,6 +336,7 @@ public function __construct(EntityManagerInterface $em)
*/
public function commit($entity = null)
{
$this->flushing = true;
// Raise preFlush
if ($this->evm->hasListeners(Events::preFlush)) {
$this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
Expand All @@ -345,7 +361,7 @@ public function commit($entity = null)
$this->orphanRemovals)) {
$this->dispatchOnFlushEvent();
$this->dispatchPostFlushEvent();

$this->flushing = false;
return; // Nothing to do.
}

Expand Down Expand Up @@ -404,6 +420,7 @@ public function commit($entity = null)
$conn->rollBack();

$this->afterTransactionRolledBack();
$this->flushing = false;

throw $e;
}
Expand All @@ -428,6 +445,7 @@ public function commit($entity = null)
$this->visitedCollections =
$this->scheduledForSynchronization =
$this->orphanRemovals = array();
$this->flushing = false;
}

/**
Expand Down Expand Up @@ -471,8 +489,13 @@ private function computeSingleEntityChangeSet($entity)
if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) {
$this->persist($entity);
}
$oid = spl_object_hash($entity);

// Compute changes for INSERTed entities first. This must always happen even in this case.
if (isset($this->entityShouldBeInserted[$oid])) {
$this->entityInsertions[$oid] = $entity;
unset($this->entityShouldBeInserted[$oid]);
}
$this->computeScheduleInsertsChangeSets();

if ($class->isReadOnly) {
Expand All @@ -485,8 +508,6 @@ private function computeSingleEntityChangeSet($entity)
}

// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
$oid = spl_object_hash($entity);

if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
$this->computeChangeSet($class, $entity);
}
Expand Down Expand Up @@ -754,6 +775,9 @@ public function computeChangeSet(ClassMetadata $class, $entity)
public function computeChangeSets()
{
// Compute changes for INSERTed entities first. This must always happen.
$this->entityInsertions = $this->entityInsertions + $this->entityShouldBeInserted;
$this->entityShouldBeInserted = array();

$this->computeScheduleInsertsChangeSets();

// Compute changes for other MANAGED entities. Change tracking policies take effect here.
Expand Down Expand Up @@ -966,7 +990,7 @@ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
if ($changeSet) {
if (isset($this->entityChangeSets[$oid])) {
$this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
} else if ( ! isset($this->entityInsertions[$oid])) {
} else if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityShouldBeInserted[$oid])) {
$this->entityChangeSets[$oid] = $changeSet;
$this->entityUpdates[$oid] = $entity;
}
Expand Down Expand Up @@ -996,7 +1020,7 @@ private function executeInserts($class)

$persister->addInsert($entity);

unset($this->entityInsertions[$oid]);
unset($this->entityInsertions[$oid], $this->entityShouldBeInserted[$oid]);

if ($invoke !== ListenersInvoker::INVOKE_NONE) {
$entities[] = $entity;
Expand Down Expand Up @@ -1202,15 +1226,19 @@ public function scheduleForInsert($entity)
if (isset($this->entityDeletions[$oid])) {
throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
}
if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid]) && !isset($this->entityShouldBeInserted[$oid])) {
throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
}

if (isset($this->entityInsertions[$oid])) {
if (isset($this->entityInsertions[$oid]) || isset($this->entityShouldBeInserted[$oid])) {
throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
}

$this->entityInsertions[$oid] = $entity;
if ($this->flushing) {
$this->entityInsertions[$oid] = $entity;
} else {
$this->entityShouldBeInserted[$oid] = $entity;
}

if (isset($this->entityIdentifiers[$oid])) {
$this->addToIdentityMap($entity);
Expand All @@ -1230,7 +1258,7 @@ public function scheduleForInsert($entity)
*/
public function isScheduledForInsert($entity)
{
return isset($this->entityInsertions[spl_object_hash($entity)]);
return isset($this->entityInsertions[spl_object_hash($entity)]) || isset($this->entityShouldBeInserted[spl_object_hash($entity)]);
}

/**
Expand All @@ -1254,7 +1282,7 @@ public function scheduleForUpdate($entity)
throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
}

if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid]) && ! isset($this->entityShouldBeInserted[$oid])) {
$this->entityUpdates[$oid] = $entity;
}
}
Expand Down Expand Up @@ -1327,12 +1355,12 @@ public function scheduleForDelete($entity)
{
$oid = spl_object_hash($entity);

if (isset($this->entityInsertions[$oid])) {
if (isset($this->entityInsertions[$oid]) || isset($this->entityShouldBeInserted[$oid])) {
if ($this->isInIdentityMap($entity)) {
$this->removeFromIdentityMap($entity);
}

unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
unset($this->entityInsertions[$oid], $this->entityShouldBeInserted[$oid], $this->entityStates[$oid]);

return; // entity has not been persisted yet, so nothing more to do.
}
Expand Down Expand Up @@ -1377,7 +1405,8 @@ public function isEntityScheduled($entity)

return isset($this->entityInsertions[$oid])
|| isset($this->entityUpdates[$oid])
|| isset($this->entityDeletions[$oid]);
|| isset($this->entityDeletions[$oid])
|| isset($this->entityShouldBeInserted[$oid]);
}

/**
Expand Down Expand Up @@ -1984,7 +2013,8 @@ private function doDetach($entity, array &$visited, $noCascade = false)
$this->entityDeletions[$oid],
$this->entityIdentifiers[$oid],
$this->entityStates[$oid],
$this->originalEntityData[$oid]
$this->originalEntityData[$oid],
$this->entityShouldBeInserted[$oid]
);
break;
case self::STATE_NEW:
Expand Down Expand Up @@ -2040,12 +2070,15 @@ private function doRefresh($entity, array &$visited)
throw ORMInvalidArgumentException::entityNotManaged($entity);
}

$this->getEntityPersister($class->name)->refresh(
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
$entity
);
if (isset($this->entityIdentifiers[$oid])) {
$this->getEntityPersister($class->name)->refresh(
array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
$entity
);

$this->cascadeRefresh($entity, $visited);
}

$this->cascadeRefresh($entity, $visited);
}

/**
Expand Down Expand Up @@ -2376,6 +2409,7 @@ public function clear($entityName = null)
$this->entityChangeSets =
$this->entityStates =
$this->scheduledForSynchronization =
$this->entityShouldBeInserted =
$this->entityInsertions =
$this->entityUpdates =
$this->entityDeletions =
Expand Down Expand Up @@ -2964,7 +2998,7 @@ public function scheduleForDirtyCheck($entity)
*/
public function hasPendingInsertions()
{
return ! empty($this->entityInsertions);
return ! empty($this->entityInsertions) || !empty($this->entityShouldBeInserted);
}

/**
Expand Down
62 changes: 56 additions & 6 deletions tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsComment;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\Models\CMS\CmsEmail;

class BasicFunctionalTest extends OrmFunctionalTestCase
{
Expand Down Expand Up @@ -1084,8 +1085,9 @@ public function testFlushManyExplicitEntities()

$this->assertTrue($userA->id > 0, 'user a has an id');
$this->assertTrue($userB->id > 0, 'user b has an id');
$this->assertTrue($userC->id > 0, 'user c has an id');
$this->assertEquals('UserC', $userC->name, 'name has not changed because we did not flush it');
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($userC), 'user c is not flushed');

$this->assertEquals('changed name', $userC->name, 'name changed because we did not flush it, so we can not refresh it');
}

/**
Expand Down Expand Up @@ -1149,7 +1151,7 @@ public function testFlushSingleAndNewEntity()
$this->_em->flush($user);

$this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager");
$this->assertTrue($otherUser->id > 0, "other user has an id");
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($otherUser), 'other user is not flushed');
}

/**
Expand All @@ -1175,8 +1177,56 @@ public function testFlushAndCascadePersist()

$this->_em->flush($user);

$this->assertTrue($this->_em->contains($address), "Other user is contained in EntityManager");
$this->assertTrue($address->id > 0, "other user has an id");
$this->assertTrue($this->_em->contains($address), "Address is contained in EntityManager");
$this->assertTrue($address->id > 0, "Address has an id");
}

public function testFlushOnSingleEntity()
{
$user = new CmsUser;
$user->name = 'Dominik';
$user->username = 'domnikl';
$user->status = 'developer';

$this->_em->persist($user);

$email = new CmsEmail;
$email->email = '[email protected]';

$this->_em->persist($email);

$q = $this->getCurrentQueryCount();
$this->_em->flush($email);

$this->assertEquals($q+3, $this->getCurrentQueryCount());

$q = $this->getCurrentQueryCount();
$this->_em->flush($user);

$this->assertEquals($q+3, $this->getCurrentQueryCount());
}

public function testFlushOnSingleEntitySameClass()
{
$domnik = new CmsUser;
$domnik->name = 'Dominik';
$domnik->username = 'domnikl';
$domnik->status = 'developer';
$this->_em->persist($domnik);

$fabian = new CmsUser;
$fabian->name = 'Fabian';
$fabian->username = 'fabian';
$fabian->status = 'tester';
$this->_em->persist($fabian);

$q = $this->getCurrentQueryCount();
$this->_em->flush($fabian);
$this->assertEquals($q+3, $this->getCurrentQueryCount(), "Only Fabian should be flushed");

$q = $this->getCurrentQueryCount();
$this->_em->flush($domnik);
$this->assertEquals($q+3, $this->getCurrentQueryCount(), "Dominik should be flushed");
}

/**
Expand Down Expand Up @@ -1253,7 +1303,7 @@ public function testProxyIsIgnored()
$this->_em->flush($user);

$this->assertTrue($this->_em->contains($otherUser), "Other user is contained in EntityManager");
$this->assertTrue($otherUser->id > 0, "other user has an id");
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($otherUser), 'other user is not flushed');
}

/**
Expand Down

0 comments on commit fda6ff3

Please sign in to comment.