Commit ae1b9371 authored by romanb's avatar romanb

[2.0] Fixed #2366.

parent be0088f0
...@@ -413,13 +413,12 @@ class EntityManager ...@@ -413,13 +413,12 @@ class EntityManager
* Refreshes the persistent state of an entity from the database, * Refreshes the persistent state of an entity from the database,
* overriding any local changes that have not yet been persisted. * overriding any local changes that have not yet been persisted.
* *
* @param object $entity * @param object $entity The entity to refresh.
* @todo Implementation
*/ */
public function refresh($entity) public function refresh($entity)
{ {
$this->_errorIfClosed(); $this->_errorIfClosed();
throw DoctrineException::notImplemented(); $this->_unitOfWork->refresh($entity);
} }
/** /**
......
...@@ -260,7 +260,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -260,7 +260,7 @@ class ObjectHydrator extends AbstractHydrator
} else { } else {
// Eager load // Eager load
//TODO: Allow more efficient and configurable batching of these loads //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 { } else {
//TODO: Eager load //TODO: Eager load
......
...@@ -37,8 +37,18 @@ namespace Doctrine\ORM\Mapping; ...@@ -37,8 +37,18 @@ namespace Doctrine\ORM\Mapping;
*/ */
abstract class AssociationMapping 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; 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; const FETCH_EAGER = 3;
/** /**
...@@ -66,7 +76,7 @@ abstract class AssociationMapping ...@@ -66,7 +76,7 @@ abstract class AssociationMapping
* *
* @var integer * @var integer
*/ */
public $fetchMode = self::FETCH_MANUAL; public $fetchMode = self::FETCH_LAZY;
/** /**
* Flag that indicates whether the class that defines this mapping is * Flag that indicates whether the class that defines this mapping is
...@@ -244,16 +254,6 @@ abstract class AssociationMapping ...@@ -244,16 +254,6 @@ abstract class AssociationMapping
return $this->fetchMode == self::FETCH_LAZY; 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. * Whether the source entity of this association represents the owning side.
* *
......
...@@ -198,7 +198,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection ...@@ -198,7 +198,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
public function remove($key) public function remove($key)
{ {
//TODO: delete entity if shouldDeleteOrphans //TODO: delete entity if shouldDeleteOrphans
/*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans()) { /*if ($this->_association->isOneToMany() && $this->_association->shouldDeleteOrphans) {
$this->_em->remove($removed); $this->_em->remove($removed);
}*/ }*/
$removed = parent::remove($key); $removed = parent::remove($key);
...@@ -209,8 +209,8 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection ...@@ -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 used as 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 List this is like add(position,value).
* *
* @param integer $key * @param integer $key
* @param mixed $value * @param mixed $value
...@@ -302,6 +302,9 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection ...@@ -302,6 +302,9 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
} }
/**
* Initializes the collection by loading its contents from the database.
*/
private function _initialize() private function _initialize()
{ {
...@@ -375,15 +378,21 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection ...@@ -375,15 +378,21 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
{ {
//TODO: Register collection as dirty with the UoW if necessary //TODO: Register collection as dirty with the UoW if necessary
//TODO: If oneToMany() && shouldDeleteOrphan() delete entities //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) { foreach ($this->_data as $entity) {
$this->_em->remove($entity); $this->_em->remove($entity);
} }
}*/ }*/
parent::clear(); parent::clear();
if ($this->_association->isOwningSide) {
$this->_changed(); $this->_changed();
$this->_em->getUnitOfWork()->scheduleCollectionDeletion($this);
}
} }
/**
* Marks this collection as changed/dirty.
*/
private function _changed() private function _changed()
{ {
$this->_isDirty = true; $this->_isDirty = true;
......
...@@ -25,6 +25,7 @@ namespace Doctrine\ORM\Persisters; ...@@ -25,6 +25,7 @@ namespace Doctrine\ORM\Persisters;
* Persister for collections of basic elements / value types. * Persister for collections of basic elements / value types.
* *
* @author robo * @author robo
* @todo Implementation once support for collections of basic elements (i.e. strings) is added.
*/ */
class ElementCollectionPersister extends AbstractCollectionPersister class ElementCollectionPersister extends AbstractCollectionPersister
{ {
......
...@@ -108,7 +108,7 @@ class ManyToManyPersister extends AbstractCollectionPersister ...@@ -108,7 +108,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
$mapping = $coll->getMapping(); $mapping = $coll->getMapping();
$joinTable = $mapping->getJoinTable(); $joinTable = $mapping->getJoinTable();
$whereClause = ''; $whereClause = '';
foreach ($mapping->getSourceToRelationKeyColumns() as $relationColumn) { foreach ($mapping->sourceToRelationKeyColumns as $relationColumn) {
if ($whereClause !== '') $whereClause .= ' AND '; if ($whereClause !== '') $whereClause .= ' AND ';
$whereClause .= "$relationColumn = ?"; $whereClause .= "$relationColumn = ?";
} }
......
...@@ -26,18 +26,23 @@ use Doctrine\ORM\PersistentCollection; ...@@ -26,18 +26,23 @@ use Doctrine\ORM\PersistentCollection;
/** /**
* Persister for one-to-many collections. * 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 * @since 2.0
* @author Roman Borschel <roman@code-factory.org> * @author Roman Borschel <roman@code-factory.org>
* @todo Complete implementation when the support for uni-directional one-to-many mappings
* on a foreign key gets added.
*/ */
class OneToManyPersister extends AbstractCollectionPersister class OneToManyPersister extends AbstractCollectionPersister
{ {
/** /**
* {@inheritdoc} * Generates the SQL UPDATE that updates a particular row's foreign
* key to null.
* *
* @param <type> $coll * @param PersistentCollection $coll
* @return <type> * @return string
* @override * @override
*/ */
protected function _getDeleteRowSql(PersistentCollection $coll) protected function _getDeleteRowSql(PersistentCollection $coll)
...@@ -63,14 +68,53 @@ class OneToManyPersister extends AbstractCollectionPersister ...@@ -63,14 +68,53 @@ class OneToManyPersister extends AbstractCollectionPersister
return array("UPDATE $table SET $setClause WHERE $whereClause", $this->_uow->getEntityIdentifier($element)); 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"; return "UPDATE xxx SET foreign_key = yyy WHERE foreign_key = zzz";
} }
/* Not used for OneToManyPersister */ /* Not used for OneToManyPersister */
protected function _getUpdateRowSql() protected function _getUpdateRowSql(PersistentCollection $coll)
{ {
return; 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
...@@ -70,13 +70,6 @@ class StandardEntityPersister ...@@ -70,13 +70,6 @@ class StandardEntityPersister
*/ */
protected $_em; protected $_em;
/**
* The EventManager instance.
*
* @var Doctrine\Common\EventManager
*/
protected $_evm;
/** /**
* Queued inserts. * Queued inserts.
* *
...@@ -96,7 +89,6 @@ class StandardEntityPersister ...@@ -96,7 +89,6 @@ class StandardEntityPersister
{ {
$this->_em = $em; $this->_em = $em;
$this->_platform = $em->getConnection()->getDatabasePlatform(); $this->_platform = $em->getConnection()->getDatabasePlatform();
$this->_evm = $em->getEventManager();
$this->_entityName = $class->name; $this->_entityName = $class->name;
$this->_conn = $em->getConnection(); $this->_conn = $em->getConnection();
$this->_class = $class; $this->_class = $class;
...@@ -206,17 +198,9 @@ class StandardEntityPersister ...@@ -206,17 +198,9 @@ class StandardEntityPersister
); );
$tableName = $this->_class->primaryTable['name']; $tableName = $this->_class->primaryTable['name'];
if ($this->_evm->hasListeners(Events::preUpdate)) {
$this->_preUpdate($entity);
}
if (isset($updateData[$tableName]) && $updateData[$tableName]) { if (isset($updateData[$tableName]) && $updateData[$tableName]) {
$this->_doUpdate($entity, $tableName, $updateData[$tableName], $id); $this->_doUpdate($entity, $tableName, $updateData[$tableName], $id);
} }
if ($this->_evm->hasListeners(Events::postUpdate)) {
$this->_postUpdate($entity);
}
} }
/** /**
...@@ -458,7 +442,9 @@ class StandardEntityPersister ...@@ -458,7 +442,9 @@ class StandardEntityPersister
$proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues); $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues);
$this->_class->reflFields[$field]->setValue($entity, $proxy); $this->_class->reflFields[$field]->setValue($entity, $proxy);
} else { } 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 { } else {
// Inject collection // Inject collection
......
...@@ -1310,6 +1310,71 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1310,6 +1310,71 @@ class UnitOfWork implements PropertyChangedListener
return $managedCopy; 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. * Cascades a merge operation to associated entities.
* *
...@@ -1324,7 +1389,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1324,7 +1389,7 @@ class UnitOfWork implements PropertyChangedListener
if ( ! $assocMapping->isCascadeMerge) { if ( ! $assocMapping->isCascadeMerge) {
continue; continue;
} }
$relatedEntities = $class->reflFields[$assocMapping->getSourceFieldName()] $relatedEntities = $class->reflFields[$assocMapping->sourceFieldName]
->getValue($entity); ->getValue($entity);
if ($relatedEntities instanceof Collection) { if ($relatedEntities instanceof Collection) {
foreach ($relatedEntities as $relatedEntity) { foreach ($relatedEntities as $relatedEntity) {
......
...@@ -173,8 +173,8 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -173,8 +173,8 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
array())->fetchColumn(); array())->fetchColumn();
$this->assertEquals(10, $count); $this->assertEquals(10, $count);
//$user->groups->clear(); $user->groups->clear();
unset($user->groups); //unset($user->groups);
$this->_em->flush(); $this->_em->flush();
...@@ -184,6 +184,35 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -184,6 +184,35 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, $count); $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() public function testBasicQuery()
{ {
$user = new CmsUser; $user = new CmsUser;
...@@ -304,4 +333,21 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -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"); $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())); $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
...@@ -83,7 +83,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional ...@@ -83,7 +83,7 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional
$this->_createFixture(); $this->_createFixture();
$this->_em->getConfiguration()->setAllowPartialObjects(false); $this->_em->getConfiguration()->setAllowPartialObjects(false);
$metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer'); $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'); $query = $this->_em->createQuery('select c from Doctrine\Tests\Models\ECommerce\ECommerceCustomer c');
$result = $query->getResultList(); $result = $query->getResultList();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment