Merge pull request #166 from doctrine/DDC-720
DDC-720 - Add support to flush only one entity through EntityManager#flush()
This commit is contained in:
commit
fd00f2d371
@ -325,13 +325,17 @@ class EntityManager implements ObjectManager
|
|||||||
* This effectively synchronizes the in-memory state of managed objects with the
|
* This effectively synchronizes the in-memory state of managed objects with the
|
||||||
* database.
|
* database.
|
||||||
*
|
*
|
||||||
|
* If an entity is explicitly passed to this method only this entity and
|
||||||
|
* the cascade-persist semantics + scheduled inserts/removals are synchronized.
|
||||||
|
*
|
||||||
|
* @param object $entity
|
||||||
* @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
* @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
||||||
* makes use of optimistic locking fails.
|
* makes use of optimistic locking fails.
|
||||||
*/
|
*/
|
||||||
public function flush()
|
public function flush($entity = null)
|
||||||
{
|
{
|
||||||
$this->errorIfClosed();
|
$this->errorIfClosed();
|
||||||
$this->unitOfWork->commit();
|
$this->unitOfWork->commit($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -254,11 +254,17 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
* 4) All collection updates
|
* 4) All collection updates
|
||||||
* 5) All entity deletions
|
* 5) All entity deletions
|
||||||
*
|
*
|
||||||
|
* @param object $entity
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function commit()
|
public function commit($entity = null)
|
||||||
{
|
{
|
||||||
// Compute changes done since last commit.
|
// Compute changes done since last commit.
|
||||||
|
if ($entity === null) {
|
||||||
$this->computeChangeSets();
|
$this->computeChangeSets();
|
||||||
|
} else {
|
||||||
|
$this->computeSingleEntityChangeSet($entity);
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! ($this->entityInsertions ||
|
if ( ! ($this->entityInsertions ||
|
||||||
$this->entityDeletions ||
|
$this->entityDeletions ||
|
||||||
@ -347,6 +353,61 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
$this->orphanRemovals = array();
|
$this->orphanRemovals = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the changesets of all entities scheduled for insertion
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function computeScheduleInsertsChangeSets()
|
||||||
|
{
|
||||||
|
foreach ($this->entityInsertions as $entity) {
|
||||||
|
$class = $this->em->getClassMetadata(get_class($entity));
|
||||||
|
$this->computeChangeSet($class, $entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only flush the given entity according to a ruleset that keeps the UoW consistent.
|
||||||
|
*
|
||||||
|
* 1. All entities scheduled for insertion, (orphan) removals and changes in collections are processed as well!
|
||||||
|
* 2. Read Only entities are skipped.
|
||||||
|
* 3. Proxies are skipped.
|
||||||
|
* 4. Only if entity is properly managed.
|
||||||
|
*
|
||||||
|
* @param object $entity
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function computeSingleEntityChangeSet($entity)
|
||||||
|
{
|
||||||
|
if ( ! $this->isInIdentityMap($entity) ) {
|
||||||
|
throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = $this->em->getClassMetadata(get_class($entity));
|
||||||
|
|
||||||
|
if ($class->isChangeTrackingDeferredImplicit()) {
|
||||||
|
$this->persist($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute changes for INSERTed entities first. This must always happen even in this case.
|
||||||
|
$this->computeScheduleInsertsChangeSets();
|
||||||
|
|
||||||
|
if ( $class->isReadOnly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore uninitialized proxy objects
|
||||||
|
if ($entity instanceof Proxy && ! $entity->__isInitialized__) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here.
|
||||||
|
$oid = spl_object_hash($entity);
|
||||||
|
if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) {
|
||||||
|
$this->computeChangeSet($class, $entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes any extra updates that have been scheduled.
|
* Executes any extra updates that have been scheduled.
|
||||||
*/
|
*/
|
||||||
@ -526,10 +587,7 @@ class UnitOfWork implements PropertyChangedListener
|
|||||||
public function computeChangeSets()
|
public function computeChangeSets()
|
||||||
{
|
{
|
||||||
// Compute changes for INSERTed entities first. This must always happen.
|
// Compute changes for INSERTed entities first. This must always happen.
|
||||||
foreach ($this->entityInsertions as $entity) {
|
$this->computeScheduleInsertsChangeSets();
|
||||||
$class = $this->em->getClassMetadata(get_class($entity));
|
|
||||||
$this->computeChangeSet($class, $entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ($this->identityMap as $className => $entities) {
|
foreach ($this->identityMap as $className => $entities) {
|
||||||
|
@ -1030,4 +1030,173 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
|||||||
|
|
||||||
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address));
|
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_DETACHED, $unitOfWork->getEntityState($address));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testFlushSingleManagedEntity()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$user->status = 'administrator';
|
||||||
|
$this->_em->flush($user);
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$user = $this->_em->find(get_class($user), $user->id);
|
||||||
|
$this->assertEquals('administrator', $user->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testFlushSingleUnmanagedEntity()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$this->setExpectedException('InvalidArgumentException', 'Entity has to be managed for single computation');
|
||||||
|
$this->_em->flush($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testFlushSingleAndNewEntity()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$otherUser = new CmsUser;
|
||||||
|
$otherUser->name = 'Dominik2';
|
||||||
|
$otherUser->username = 'domnikl2';
|
||||||
|
$otherUser->status = 'developer';
|
||||||
|
|
||||||
|
$user->status = 'administrator';
|
||||||
|
|
||||||
|
$this->_em->persist($otherUser);
|
||||||
|
$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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testFlushAndCascadePersist()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$address = new CmsAddress();
|
||||||
|
$address->city = "Springfield";
|
||||||
|
$address->zip = "12354";
|
||||||
|
$address->country = "Germany";
|
||||||
|
$address->street = "Foo Street";
|
||||||
|
$address->user = $user;
|
||||||
|
$user->address = $address;
|
||||||
|
|
||||||
|
$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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testFlushSingleAndNoCascade()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$article1 = new CmsArticle();
|
||||||
|
$article1->topic = 'Foo';
|
||||||
|
$article1->text = 'Foo Text';
|
||||||
|
$article1->author = $user;
|
||||||
|
$user->articles[] = $article1;
|
||||||
|
|
||||||
|
$this->setExpectedException('InvalidArgumentException', "A new entity was found through the relationship 'Doctrine\Tests\Models\CMS\CmsUser#articles'");
|
||||||
|
$this->_em->flush($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testProxyIsIgnored()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($user);
|
||||||
|
$this->_em->flush();
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$user = $this->_em->getReference(get_class($user), $user->id);
|
||||||
|
|
||||||
|
$otherUser = new CmsUser;
|
||||||
|
$otherUser->name = 'Dominik2';
|
||||||
|
$otherUser->username = 'domnikl2';
|
||||||
|
$otherUser->status = 'developer';
|
||||||
|
|
||||||
|
$this->_em->persist($otherUser);
|
||||||
|
$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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group DDC-720
|
||||||
|
*/
|
||||||
|
public function testFlushSingleSaveOnlySingle()
|
||||||
|
{
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->name = 'Dominik';
|
||||||
|
$user->username = 'domnikl';
|
||||||
|
$user->status = 'developer';
|
||||||
|
$this->_em->persist($user);
|
||||||
|
|
||||||
|
$user2 = new CmsUser;
|
||||||
|
$user2->name = 'Dominik';
|
||||||
|
$user2->username = 'domnikl2';
|
||||||
|
$user2->status = 'developer';
|
||||||
|
$this->_em->persist($user2);
|
||||||
|
|
||||||
|
$this->_em->flush();
|
||||||
|
|
||||||
|
$user->status = 'admin';
|
||||||
|
$user2->status = 'admin';
|
||||||
|
|
||||||
|
$this->_em->flush($user);
|
||||||
|
$this->_em->clear();
|
||||||
|
|
||||||
|
$user2 = $this->_em->find(get_class($user2), $user2->id);
|
||||||
|
$this->assertEquals('developer', $user2->status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user