1
0
mirror of synced 2025-01-18 22:41:43 +03:00

A little progress on the UnitOfWork.

This commit is contained in:
romanb 2009-01-29 17:00:44 +00:00
parent 0ac97e7adf
commit 36763dadb6
12 changed files with 196 additions and 151 deletions

View File

@ -9,6 +9,7 @@ namespace Doctrine\Common\Collections;
use \Countable; use \Countable;
use \IteratorAggregate; use \IteratorAggregate;
use \ArrayAccess; use \ArrayAccess;
use \ArrayIterator;
/** /**
* A Collection is a wrapper around a php array and just like a php array a * A Collection is a wrapper around a php array and just like a php array a
@ -88,6 +89,22 @@ class Collection implements Countable, IteratorAggregate, ArrayAccess
return $removed; return $removed;
} }
/**
* Removes the specified element from the collection, if it is found.
*
* @param mixed $element
* @return boolean
*/
public function removeElement($element)
{
$key = array_search($element, $this->_data, true);
if ($key !== false) {
unset($this->_data[$key]);
return true;
}
return false;
}
/** /**
* @see containsKey() * @see containsKey()
*/ */
@ -174,7 +191,7 @@ class Collection implements Countable, IteratorAggregate, ArrayAccess
* Tests for the existance of an element that satisfies the given predicate. * Tests for the existance of an element that satisfies the given predicate.
* *
* @param function $func * @param function $func
* @return boolean TRUE if the predicate is TRUE for at least one element, FALSe otherwise. * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*/ */
public function exists(Closure $func) { public function exists(Closure $func) {
foreach ($this->_data as $key => $element) foreach ($this->_data as $key => $element)
@ -191,7 +208,7 @@ class Collection implements Countable, IteratorAggregate, ArrayAccess
*/ */
public function containsAll($otherColl) public function containsAll($otherColl)
{ {
throw new Doctrine_Exception("Not yet implemented."); throw new DoctrineException("Not yet implemented.");
} }
/** /**

View File

@ -495,7 +495,7 @@ class Connection
*/ */
public function prepare($statement) public function prepare($statement)
{ {
echo $statement; echo $statement . PHP_EOL;
$this->connect(); $this->connect();
try { try {
return $this->_conn->prepare($statement); return $this->_conn->prepare($statement);
@ -561,6 +561,7 @@ class Connection
$this->connect(); $this->connect();
try { try {
if ( ! empty($params)) { if ( ! empty($params)) {
//var_dump($params);
$stmt = $this->prepare($query); $stmt = $this->prepare($query);
$stmt->execute($params); $stmt->execute($params);
return $stmt->rowCount(); return $stmt->rowCount();

View File

@ -9,6 +9,7 @@
* most method calls to the EntityManager. * most method calls to the EntityManager.
* *
* @since 2.0 * @since 2.0
* @todo Any takers for this one? Needs a rewrite.
*/ */
class Doctrine_ORM_ActiveEntity class Doctrine_ORM_ActiveEntity
{ {

View File

@ -21,11 +21,13 @@
namespace Doctrine\ORM; namespace Doctrine\ORM;
use Doctrine\ORM\Mapping\AssociationMapping;
/** /**
* A persistent collection wrapper. * A persistent collection wrapper.
* *
* A PersistentCollection represents a collection of entities. Collections of * A PersistentCollection represents a collection of elements that have persistent state.
* entities represent only the associations (links) to those entities. * Collections of entities represent only the associations (links) to those entities.
* That means, if the collection is part of a many-many mapping and you remove * That means, if the collection is part of a many-many mapping and you remove
* entities from the collection, only the links in the xref table are removed (on flush). * entities from the collection, only the links in the xref table are removed (on flush).
* Similarly, if you remove entities from a collection that is part of a one-many * Similarly, if you remove entities from a collection that is part of a one-many
@ -104,13 +106,17 @@ final class Collection extends \Doctrine\Common\Collections\Collection
*/ */
private $_hydrationFlag; private $_hydrationFlag;
/**
* The class descriptor of the owning entity.
*/
private $_ownerClass; private $_ownerClass;
/** /**
* Creates a new persistent collection. * Creates a new persistent collection.
*/ */
public function __construct(EntityManager $em, $entityBaseType, $keyField = null) public function __construct(EntityManager $em, $entityBaseType, array $data = array(), $keyField = null)
{ {
parent::__construct($data);
$this->_entityBaseType = $entityBaseType; $this->_entityBaseType = $entityBaseType;
$this->_em = $em; $this->_em = $em;
$this->_ownerClass = $em->getClassMetadata($entityBaseType); $this->_ownerClass = $em->getClassMetadata($entityBaseType);
@ -151,7 +157,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection
* @param object $entity * @param object $entity
* @param AssociationMapping $relation * @param AssociationMapping $relation
*/ */
public function _setOwner($entity, \Doctrine\ORM\Mapping\AssociationMapping $relation) public function _setOwner($entity, AssociationMapping $relation)
{ {
$this->_owner = $entity; $this->_owner = $entity;
$this->_association = $relation; $this->_association = $relation;
@ -180,7 +186,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection
} }
/** /**
* Removes an entity from the collection. * Removes an element from the collection.
* *
* @param mixed $key * @param mixed $key
* @return boolean * @return boolean
@ -215,11 +221,11 @@ final class Collection extends \Doctrine\Common\Collections\Collection
} }
/** /**
* Adds an entry to the collection. * Adds an element to the collection.
* *
* @param mixed $value * @param mixed $value
* @param string $key * @param string $key
* @return boolean * @return TRUE
* @override * @override
*/ */
public function add($value) public function add($value)
@ -230,8 +236,8 @@ final class Collection extends \Doctrine\Common\Collections\Collection
if ($this->_hydrationFlag) { if ($this->_hydrationFlag) {
if ($this->_backRefFieldName) { if ($this->_backRefFieldName) {
// set back reference to owner // set back reference to owner
$this->_ownerClass->getReflectionProperty( $this->_ownerClass->getReflectionProperty($this->_backRefFieldName)
$this->_backRefFieldName)->setValue($value, $this->_owner); ->setValue($value, $this->_owner);
} }
} else { } else {
//TODO: Register collection as dirty with the UoW if necessary //TODO: Register collection as dirty with the UoW if necessary
@ -295,27 +301,6 @@ final class Collection extends \Doctrine\Common\Collections\Collection
return $this->_snapshot; return $this->_snapshot;
} }
/**
* INTERNAL:
* Processes the difference of the last snapshot and the current data.
*
* an example:
* Snapshot with the objects 1, 2 and 4
* Current data with objects 2, 3 and 5
*
* The process would remove objects 1 and 4
*
* @return Doctrine_Collection
* @todo Move elsewhere
*/
public function processDiff()
{
foreach (array_udiff($this->_snapshot, $this->_data, array($this, "_compareRecords")) as $record) {
$record->delete();
}
return $this;
}
/** /**
* INTERNAL: * INTERNAL:
* getDeleteDiff * getDeleteDiff
@ -324,7 +309,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection
*/ */
public function getDeleteDiff() public function getDeleteDiff()
{ {
return array_udiff($this->_snapshot, $this->_data, array($this, "_compareRecords")); return array_udiff($this->_snapshot, $this->_data, array($this, '_compareRecords'));
} }
/** /**
@ -334,7 +319,7 @@ final class Collection extends \Doctrine\Common\Collections\Collection
*/ */
public function getInsertDiff() public function getInsertDiff()
{ {
return array_udiff($this->_data, $this->_snapshot, array($this, "_compareRecords")); return array_udiff($this->_data, $this->_snapshot, array($this, '_compareRecords'));
} }
/** /**
@ -377,8 +362,10 @@ final class Collection extends \Doctrine\Common\Collections\Collection
private function _changed() private function _changed()
{ {
/*if ( ! $this->_em->getUnitOfWork()->isCollectionScheduledForUpdate($this)) { if ( ! $this->_em->getUnitOfWork()->isCollectionScheduledForUpdate($this)) {
$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); //var_dump(get_class($this->_snapshot[0]));
}*/ //echo "NOT!";
//$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this);
}
} }
} }

View File

@ -54,7 +54,7 @@ class ObjectHydrator extends AbstractHydrator
if ($this->_parserResult->isMixedQuery()) { if ($this->_parserResult->isMixedQuery()) {
$result = array(); $result = array();
} else { } else {
$result = new \Doctrine\ORM\Collection($this->_em, $this->_rootEntityName); $result = new \Doctrine\Common\Collections\Collection;
} }
$cache = array(); $cache = array();
@ -66,7 +66,7 @@ class ObjectHydrator extends AbstractHydrator
foreach ($this->_collections as $coll) { foreach ($this->_collections as $coll) {
$coll->_takeSnapshot(); $coll->_takeSnapshot();
$coll->_setHydrationFlag(false); $coll->_setHydrationFlag(false);
$this->_uow->addManagedCollection($coll); //$this->_uow->addManagedCollection($coll);
} }
// Clean up // Clean up
@ -105,7 +105,7 @@ class ObjectHydrator extends AbstractHydrator
if ( ! is_object($coll)) { if ( ! is_object($coll)) {
end($coll); end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)]; $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
} else if ($coll instanceof \Doctrine\ORM\Collection) { } else if ($coll instanceof \Doctrine\Common\Collections\Collection) {
if (count($coll) > 0) { if (count($coll) > 0) {
$this->_resultPointers[$dqlAlias] = $coll->last(); $this->_resultPointers[$dqlAlias] = $coll->last();
} }

View File

@ -1,6 +1,10 @@
<?php <?php
class Doctrine_ORM_Persisters_AbstractCollectionPersister namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\Collection;
class AbstractCollectionPersister
{ {
public function recreate(Doctrine_Collection $coll) public function recreate(Doctrine_Collection $coll)
@ -8,7 +12,6 @@ class Doctrine_ORM_Persisters_AbstractCollectionPersister
if ($coll->getRelation()->isInverseSide()) { if ($coll->getRelation()->isInverseSide()) {
return; return;
} }
//... //...
} }
@ -17,32 +20,39 @@ class Doctrine_ORM_Persisters_AbstractCollectionPersister
if ($coll->getRelation()->isInverseSide()) { if ($coll->getRelation()->isInverseSide()) {
return; return;
} }
//... //...
if ($coll->getRelation() instanceof Doctrine_Association_OneToManyMapping) {
//...
} else if ($coll->getRelation() instanceof Doctrine_Association_ManyToManyMapping) {
//...
}
} }
/* collection update actions */ /* collection update actions */
public function deleteRows() public function deleteRows(Collection $coll)
{
//$collection->getDeleteDiff();
}
public function updateRows(Collection $coll)
{ {
} }
public function updateRows() public function insertRows(Collection $coll)
{
//$collection->getInsertDiff();
}
protected function _getDeleteRowSql()
{ {
} }
public function insertRows() protected function _getUpdateRowSql()
{ {
} }
protected function _getDeleteRowSql()
{
}
} }
?>

View File

@ -33,12 +33,7 @@ namespace Doctrine\ORM\Persisters;
abstract class AbstractEntityPersister abstract class AbstractEntityPersister
{ {
/** /**
* The names of all the fields that are available on entities. * Metadata object that describes the mapping of the mapped entity class.
*/
protected $_fieldNames = array();
/**
* Metadata object that descibes the mapping of the mapped entity class.
* *
* @var Doctrine\ORM\Mapping\ClassMetadata * @var Doctrine\ORM\Mapping\ClassMetadata
*/ */
@ -124,25 +119,13 @@ abstract class AbstractEntityPersister
/** /**
* *
* @return <type> * @return Doctrine\ORM\ClassMetadata
*/ */
public function getClassMetadata() public function getClassMetadata()
{ {
return $this->_classMetadata; return $this->_classMetadata;
} }
/**
* @todo Move to ClassMetadata?
*/
public function getFieldNames()
{
if ($this->_fieldNames) {
return $this->_fieldNames;
}
$this->_fieldNames = $this->_classMetadata->getFieldNames();
return $this->_fieldNames;
}
/** /**
* Gets the name of the class in the entity hierarchy that owns the field with * Gets the name of the class in the entity hierarchy that owns the field with
* the given name. The owning class is the one that defines the field. * the given name. The owning class is the one that defines the field.
@ -156,15 +139,10 @@ abstract class AbstractEntityPersister
if ($this->_classMetadata->isInheritanceTypeNone()) { if ($this->_classMetadata->isInheritanceTypeNone()) {
return $this->_classMetadata; return $this->_classMetadata;
} else { } else {
foreach ($this->_classMetadata->getParentClasses() as $parentClass) { $mapping = $this->_classMetadata->getFieldMapping($fieldName);
$parentClassMetadata = Doctrine_ORM_Mapping_ClassMetadataFactory::getInstance() return $mapping['inherited'];
->getMetadataFor($parentClass);
if ( ! $parentClassMetadata->isInheritedField($fieldName)) {
return $parentClassMetadata;
} }
} throw new DoctrineException("Unable to find defining class of field '$fieldName'.");
}
throw new Doctrine_Exception("Unable to find defining class of field '$fieldName'.");
} }
/** /**
@ -186,8 +164,9 @@ abstract class AbstractEntityPersister
/** /**
* Prepares all the entity data for insertion into the database. * Prepares all the entity data for insertion into the database.
* *
* @param object $entity
* @param array $array * @param array $array
* @return void * @param boolean $isInsert
*/ */
protected function _prepareData($entity, array &$result, $isInsert = false) protected function _prepareData($entity, array &$result, $isInsert = false)
{ {

View File

@ -23,6 +23,7 @@ namespace Doctrine\ORM;
use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Internal\CommitOrderCalculator;
use Doctrine\ORM\Internal\CommitOrderNode; use Doctrine\ORM\Internal\CommitOrderNode;
use Doctrine\ORM\Collection;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Exceptions\UnitOfWorkException; use Doctrine\ORM\Exceptions\UnitOfWorkException;
@ -110,6 +111,12 @@ class UnitOfWork
*/ */
protected $_entityStates = array(); protected $_entityStates = array();
/**
* Map of entities that are scheduled for dirty checking at commit time.
* This is only used if automatic dirty checking is disabled.
*/
protected $_scheduledForDirtyCheck = array();
/** /**
* A list of all new entities that need to be INSERTed. * A list of all new entities that need to be INSERTed.
* *
@ -186,15 +193,19 @@ class UnitOfWork
} }
/** /**
* Commits the unit of work, executing all operations that have been postponed * Commits the UnitOfWork, executing all operations that have been postponed
* up to this point. * up to this point.
*
* @return void
*/ */
public function commit() public function commit()
{ {
// Compute changes in managed entities // Compute changes in managed entities
$this->computeEntityChangeSets(); $this->computeChangeSets();
/*foreach ($this->_managedCollections as $coll) {
if ($coll->isDirty()) {
}
}*/
if (empty($this->_newEntities) && if (empty($this->_newEntities) &&
empty($this->_deletedEntities) && empty($this->_deletedEntities) &&
@ -214,18 +225,18 @@ class UnitOfWork
$this->_executeUpdates($class); $this->_executeUpdates($class);
} }
//TODO: collection deletions //TODO: collection deletions (deletions of complete collections)
//TODO: collection updates (deleteRows, updateRows, insertRows) //TODO: collection updates (deleteRows, updateRows, insertRows on join tables)
//TODO: collection recreations //TODO: collection recreations (insertions of complete collections)
// Entity deletions come last and need to be in reverse commit order // Entity deletions come last and need to be in reverse commit order
for ($count = count($commitOrder), $i = $count - 1; $i >= 0; $i--) { for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) {
$this->_executeDeletions($commitOrder[$i]); $this->_executeDeletions($commitOrder[$i]);
} }
//TODO: commit transaction here? //TODO: commit transaction here?
// clear up // Clear up
$this->_newEntities = array(); $this->_newEntities = array();
$this->_dirtyEntities = array(); $this->_dirtyEntities = array();
$this->_deletedEntities = array(); $this->_deletedEntities = array();
@ -253,9 +264,11 @@ class UnitOfWork
* *
* @param array $entities The entities for which to compute the changesets. If this * @param array $entities The entities for which to compute the changesets. If this
* parameter is not specified, the changesets of all entities in the identity * parameter is not specified, the changesets of all entities in the identity
* map are computed. * map are computed if automatic dirty checking is enabled (the default).
* If automatic dirty checking is disabled, only those changesets will be
* computed that have been scheduled through scheduleForDirtyCheck().
*/ */
public function computeEntityChangeSets(array $entities = null) public function computeChangeSets(array $entities = null)
{ {
$entitySet = array(); $entitySet = array();
if ( ! is_null($entities)) { if ( ! is_null($entities)) {
@ -263,21 +276,20 @@ class UnitOfWork
$entitySet[get_class($entity)][] = $entity; $entitySet[get_class($entity)][] = $entity;
} }
} else if ( ! $this->_em->getConfiguration()->getAutomaticDirtyChecking()) { } else if ( ! $this->_em->getConfiguration()->getAutomaticDirtyChecking()) {
//TODO $entitySet = $this->_scheduledForDirtyCheck;
} else { } else {
$entitySet = $this->_identityMap; $entitySet = $this->_identityMap;
} }
foreach ($entitySet as $className => $entities) { foreach ($entitySet as $className => $entities) {
$class = $this->_em->getClassMetadata($className); $class = $this->_em->getClassMetadata($className);
if ( ! $class->isInheritanceTypeNone() && count($entities) > 0) {
$class = $this->_em->getClassMetadata(get_class($entities[0]));
}
foreach ($entities as $entity) { foreach ($entities as $entity) {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$state = $this->getEntityState($entity); $state = $this->getEntityState($entity);
if ($state == self::STATE_MANAGED || $state == self::STATE_NEW) { if ($state == self::STATE_MANAGED || $state == self::STATE_NEW) {
if ( ! $class->isInheritanceTypeNone()) {
$class = $this->_em->getClassMetadata(get_class($entity));
}
$actualData = array(); $actualData = array();
foreach ($class->getReflectionProperties() as $name => $refProp) { foreach ($class->getReflectionProperties() as $name => $refProp) {
$actualData[$name] = $refProp->getValue($entity); $actualData[$name] = $refProp->getValue($entity);
@ -294,6 +306,7 @@ class UnitOfWork
$originalData = $this->_originalEntityData[$oid]; $originalData = $this->_originalEntityData[$oid];
$changeSet = array(); $changeSet = array();
$entityIsDirty = false; $entityIsDirty = false;
foreach ($actualData as $propName => $actualValue) { foreach ($actualData as $propName => $actualValue) {
$orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
if (is_object($orgValue) && $orgValue !== $actualValue) { if (is_object($orgValue) && $orgValue !== $actualValue) {
@ -307,8 +320,16 @@ class UnitOfWork
$assoc = $class->getAssociationMapping($propName); $assoc = $class->getAssociationMapping($propName);
if ($assoc->isOneToOne() && $assoc->isOwningSide()) { if ($assoc->isOneToOne() && $assoc->isOwningSide()) {
$entityIsDirty = true; $entityIsDirty = true;
} else if ( ! $assoc->isOneToOne()) {
if ( ! $actualValue instanceof Collection) {
// Inject PersistentCollection
$coll = new Collection($this->_em, $assoc->getTargetEntityName(), $actualValue);
//$coll->_takeSnapshot();
$class->getReflectionProperty($propName)->setValue($entity, $coll);
$actualData[$propName] = $coll;
} }
$this->_handleAssociationValueChanged($assoc, $actualValue); }
//$this->_handleAssociationValueChanged($assoc, $actualValue);
} else { } else {
$entityIsDirty = true; $entityIsDirty = true;
} }
@ -322,6 +343,16 @@ class UnitOfWork
$this->_originalEntityData[$oid] = $actualData; $this->_originalEntityData[$oid] = $actualData;
} }
} }
// Look for changes in associations
if ($state == self::STATE_MANAGED) {
foreach ($class->getAssociationMappings() as $assoc) {
$val = $actualData[$assoc->getSourceFieldName()];
if ( ! is_null($val)) {
$this->_computeAssociationChanges($assoc, $val);
}
}
}
} }
} }
} }
@ -336,7 +367,7 @@ class UnitOfWork
* @param <type> $assoc * @param <type> $assoc
* @param <type> $value * @param <type> $value
*/ */
private function _handleAssociationValueChanged($assoc, $value) private function _computeAssociationChanges($assoc, $value)
{ {
if ($assoc->isOneToOne()) { if ($assoc->isOneToOne()) {
$value = array($value); $value = array($value);
@ -376,7 +407,6 @@ class UnitOfWork
// MANAGED associated entities are already taken into account // MANAGED associated entities are already taken into account
// during changeset calculation anyway, since they are in the identity map. // during changeset calculation anyway, since they are in the identity map.
} }
} }
/** /**
@ -396,6 +426,7 @@ class UnitOfWork
if (get_class($entity) == $className) { if (get_class($entity) == $className) {
$returnVal = $persister->insert($entity); $returnVal = $persister->insert($entity);
if ( ! is_null($returnVal)) { if ( ! is_null($returnVal)) {
// Persister returned a post-insert ID
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$idField = $class->getSingleIdentifierFieldName(); $idField = $class->getSingleIdentifierFieldName();
$class->getReflectionProperty($idField)->setValue($entity, $returnVal); $class->getReflectionProperty($idField)->setValue($entity, $returnVal);
@ -454,11 +485,6 @@ class UnitOfWork
$this->_deletedEntities); $this->_deletedEntities);
} }
/* if (count($entityChangeSet) == 1) {
* return array($entityChangeSet[0]->getClass());
* }
*/
// TODO: We can cache computed commit orders in the metadata cache! // TODO: We can cache computed commit orders in the metadata cache!
// Check cache at this point here! // Check cache at this point here!
@ -830,7 +856,7 @@ class UnitOfWork
/** /**
* Saves an entity as part of the current unit of work. * Saves an entity as part of the current unit of work.
* *
* @param Doctrine\ORM\Entity $entity The entity to save. * @param object $entity The entity to save.
*/ */
public function save($entity) public function save($entity)
{ {
@ -839,8 +865,8 @@ class UnitOfWork
$this->_doSave($entity, $visited, $insertNow); $this->_doSave($entity, $visited, $insertNow);
if ( ! empty($insertNow)) { if ( ! empty($insertNow)) {
// We have no choice. This means that there are new entities // We have no choice. This means that there are new entities
// with an IDENTITY column key generation strategy. // with a post-insert ID generation strategy.
$this->computeEntityChangeSets($insertNow); $this->computeChangeSets($insertNow);
$commitOrder = $this->_getCommitOrder($insertNow); $commitOrder = $this->_getCommitOrder($insertNow);
foreach ($commitOrder as $class) { foreach ($commitOrder as $class) {
$this->_executeInserts($class); $this->_executeInserts($class);
@ -858,6 +884,8 @@ class UnitOfWork
* *
* @param object $entity The entity to save. * @param object $entity The entity to save.
* @param array $visited The already visited entities. * @param array $visited The already visited entities.
* @param array $insertNow The entities that must be immediately inserted because of
* post-insert ID generation.
*/ */
private function _doSave($entity, array &$visited, array &$insertNow) private function _doSave($entity, array &$visited, array &$insertNow)
{ {
@ -871,7 +899,10 @@ class UnitOfWork
$class = $this->_em->getClassMetadata(get_class($entity)); $class = $this->_em->getClassMetadata(get_class($entity));
switch ($this->getEntityState($entity)) { switch ($this->getEntityState($entity)) {
case self::STATE_MANAGED: case self::STATE_MANAGED:
// nothing to do // nothing to do, except if automatic dirty checking is disabled
if ( ! $this->_em->getConfiguration()->getAutomaticDirtyChecking()) {
$this->scheduleForDirtyCheck($entity);
}
break; break;
case self::STATE_NEW: case self::STATE_NEW:
$idGen = $this->_em->getIdGenerator($class->getClassName()); $idGen = $this->_em->getIdGenerator($class->getClassName());
@ -907,7 +938,6 @@ class UnitOfWork
//TODO: throw UnitOfWorkException::invalidEntityState() //TODO: throw UnitOfWorkException::invalidEntityState()
throw new Doctrine_Exception("Encountered invalid entity state."); throw new Doctrine_Exception("Encountered invalid entity state.");
} }
$this->_cascadeSave($entity, $visited, $insertNow); $this->_cascadeSave($entity, $visited, $insertNow);
} }
@ -923,9 +953,11 @@ class UnitOfWork
} }
/** /**
* Enter description here... * Deletes an entity as part of the current unit of work.
* This method is internally called during delete() cascades as it tracks
* the already visited entities to prevent infinite recursions.
* *
* @param Doctrine\ORM\Entity $entity * @param object $entity
* @param array $visited * @param array $visited
*/ */
private function _doDelete($entity, array &$visited) private function _doDelete($entity, array &$visited)
@ -940,26 +972,23 @@ class UnitOfWork
switch ($this->getEntityState($entity)) { switch ($this->getEntityState($entity)) {
case self::STATE_NEW: case self::STATE_NEW:
case self::STATE_DELETED: case self::STATE_DELETED:
// nothing to do for $entity // nothing to do
break; break;
case self::STATE_MANAGED: case self::STATE_MANAGED:
$this->registerDeleted($entity); $this->registerDeleted($entity);
break; break;
case self::STATE_DETACHED: case self::STATE_DETACHED:
//exception?
throw new DoctrineException("A detached entity can't be deleted."); throw new DoctrineException("A detached entity can't be deleted.");
default: default:
//TODO: throw UnitOfWorkException::invalidEntityState()
throw new DoctrineException("Encountered invalid entity state."); throw new DoctrineException("Encountered invalid entity state.");
} }
$this->_cascadeDelete($entity, $visited); $this->_cascadeDelete($entity, $visited);
} }
/** /**
* Cascades the save operation to associated entities. * Cascades the save operation to associated entities.
* *
* @param Doctrine\ORM\Entity $entity * @param object $entity
* @param array $visited * @param array $visited
*/ */
private function _cascadeSave($entity, array &$visited, array &$insertNow) private function _cascadeSave($entity, array &$visited, array &$insertNow)
@ -971,7 +1000,7 @@ class UnitOfWork
} }
$relatedEntities = $class->getReflectionProperty($assocMapping->getSourceFieldName()) $relatedEntities = $class->getReflectionProperty($assocMapping->getSourceFieldName())
->getValue($entity); ->getValue($entity);
if (($relatedEntities instanceof Doctrine_ORM_Collection || is_array($relatedEntities)) if (($relatedEntities instanceof \Doctrine\Common\Collections\Collection || is_array($relatedEntities))
&& count($relatedEntities) > 0) { && count($relatedEntities) > 0) {
foreach ($relatedEntities as $relatedEntity) { foreach ($relatedEntities as $relatedEntity) {
$this->_doSave($relatedEntity, $visited, $insertNow); $this->_doSave($relatedEntity, $visited, $insertNow);
@ -985,9 +1014,9 @@ class UnitOfWork
/** /**
* Cascades the delete operation to associated entities. * Cascades the delete operation to associated entities.
* *
* @param Doctrine\ORM\Entity $entity * @param object $entity
*/ */
private function _cascadeDelete($entity) private function _cascadeDelete($entity, array &$visited)
{ {
$class = $this->_em->getClassMetadata(get_class($entity)); $class = $this->_em->getClassMetadata(get_class($entity));
foreach ($class->getAssociationMappings() as $assocMapping) { foreach ($class->getAssociationMappings() as $assocMapping) {
@ -996,13 +1025,13 @@ class UnitOfWork
} }
$relatedEntities = $class->getReflectionProperty($assocMapping->getSourceFieldName()) $relatedEntities = $class->getReflectionProperty($assocMapping->getSourceFieldName())
->getValue($entity); ->getValue($entity);
if ($relatedEntities instanceof \Doctrine\ORM\Collection && if ($relatedEntities instanceof \Doctrine\Common\Collections\Collection &&
count($relatedEntities) > 0) { count($relatedEntities) > 0) {
foreach ($relatedEntities as $relatedEntity) { foreach ($relatedEntities as $relatedEntity) {
$this->_doDelete($relatedEntity, $visited, $insertNow); $this->_doDelete($relatedEntity, $visited);
} }
} else if (is_object($relatedEntities)) { } else if (is_object($relatedEntities)) {
$this->_doDelete($relatedEntities, $visited, $insertNow); $this->_doDelete($relatedEntities, $visited);
} }
} }
} }
@ -1026,34 +1055,34 @@ class UnitOfWork
$this->_commitOrderCalculator->clear(); $this->_commitOrderCalculator->clear();
} }
public function scheduleCollectionUpdate(Doctrine\ORM\Collection $coll) public function scheduleCollectionUpdate(Collection $coll)
{ {
$this->_collectionUpdates[] = $coll; $this->_collectionUpdates[] = $coll;
} }
public function isCollectionScheduledForUpdate(Doctrine\ORM\Collection $coll) public function isCollectionScheduledForUpdate(Collection $coll)
{ {
//... //...
} }
public function scheduleCollectionDeletion(Doctrine\ORM\Collection $coll) public function scheduleCollectionDeletion(Collection $coll)
{ {
//TODO: if $coll is already scheduled for recreation ... what to do? //TODO: if $coll is already scheduled for recreation ... what to do?
// Just remove $coll from the scheduled recreations? // Just remove $coll from the scheduled recreations?
$this->_collectionDeletions[] = $coll; $this->_collectionDeletions[] = $coll;
} }
public function isCollectionScheduledForDeletion(Doctrine\ORM\Collection $coll) public function isCollectionScheduledForDeletion(Collection $coll)
{ {
//... //...
} }
public function scheduleCollectionRecreation(Doctrine\ORM\Collection $coll) public function scheduleCollectionRecreation(Collection $coll)
{ {
$this->_collectionRecreations[] = $coll; $this->_collectionRecreations[] = $coll;
} }
public function isCollectionScheduledForRecreation(Doctrine\ORM\Collection $coll) public function isCollectionScheduledForRecreation(Collection $coll)
{ {
//... //...
} }
@ -1063,7 +1092,7 @@ class UnitOfWork
* *
* @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 Doctrine\ORM\Entity * @return object
* @internal Performance-sensitive method. * @internal Performance-sensitive method.
*/ */
public function createEntity($className, array $data, $query = null) public function createEntity($className, array $data, $query = null)
@ -1104,7 +1133,7 @@ class UnitOfWork
* Merges the given data into the given entity, optionally overriding * Merges the given data into the given entity, optionally overriding
* local changes. * local changes.
* *
* @param Doctrine\ORM\Entity $entity * @param object $entity
* @param array $data * @param array $data
* @param boolean $overrideLocalChanges * @param boolean $overrideLocalChanges
*/ */
@ -1137,14 +1166,11 @@ class UnitOfWork
private function _inferCorrectClassName(array $data, $className) private function _inferCorrectClassName(array $data, $className)
{ {
$class = $this->_em->getClassMetadata($className); $class = $this->_em->getClassMetadata($className);
$discCol = $class->getDiscriminatorColumn(); $discCol = $class->getDiscriminatorColumn();
if ( ! $discCol) { if ( ! $discCol) {
return $className; return $className;
} }
$discMap = $class->getDiscriminatorMap(); $discMap = $class->getDiscriminatorMap();
if (isset($data[$discCol['name']], $discMap[$data[$discCol['name']]])) { if (isset($data[$discCol['name']], $discMap[$data[$discCol['name']]])) {
return $discMap[$data[$discCol['name']]]; return $discMap[$data[$discCol['name']]];
} else { } else {
@ -1203,10 +1229,10 @@ class UnitOfWork
* *
* @param Doctrine\ORM\Collection $coll * @param Doctrine\ORM\Collection $coll
*/ */
public function addManagedCollection(\Doctrine\ORM\Collection $coll) /*public function addManagedCollection(Collection $coll)
{ {
} }*/
/** /**
* Gets the identifier of an entity. * Gets the identifier of an entity.
@ -1234,9 +1260,17 @@ class UnitOfWork
return false; return false;
} }
public function scheduleForDirtyCheck($entity)
{
$rootClassName = $this->_em->getClassMetadata(get_class($entity))->getRootClassName();
$this->_scheduledForDirtyCheck[$rootClassName] = $entity;
}
/** /**
* Calculates the size of the UnitOfWork. The size of the UnitOfWork is the * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
* number of entities in the identity map. * number of entities in the identity map.
*
* @return integer
*/ */
public function size() public function size()
{ {

View File

@ -27,7 +27,7 @@ class CmsUser
public $name; public $name;
/** /**
* @DoctrineOneToMany(targetEntity="Doctrine\Tests\Models\CMS\CmsPhonenumber", * @DoctrineOneToMany(targetEntity="Doctrine\Tests\Models\CMS\CmsPhonenumber",
mappedBy="user", cascade={"save"}) mappedBy="user", cascade={"save", "delete"})
*/ */
public $phonenumbers; public $phonenumbers;
/** /**
@ -44,4 +44,14 @@ class CmsUser
$this->phonenumbers[] = $phone; $this->phonenumbers[] = $phone;
$phone->user = $this; $phone->user = $this;
} }
public function removePhonenumber($index) {
if (isset($this->phonenumbers[$index])) {
$ph = $this->phonenumbers[$index];
unset($this->phonenumbers[$index]);
$ph->user = null;
return true;
}
return false;
}
} }

View File

@ -42,24 +42,32 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase {
$em->flush(); $em->flush();
$this->assertTrue($em->contains($ph)); $this->assertTrue($em->contains($ph));
$this->assertTrue($em->contains($user)); $this->assertTrue($em->contains($user));
$this->assertTrue($user->phonenumbers instanceof \Doctrine\ORM\Collection);
// Update // Update name
$user->name = 'guilherme'; $user->name = 'guilherme';
$em->flush(); $em->flush();
$this->assertEquals('guilherme', $user->name); $this->assertEquals('guilherme', $user->name);
// Add another phonenumber
$ph2 = new CmsPhonenumber;
$ph2->phonenumber = "6789";
$user->addPhonenumber($ph2);
$em->flush();
$this->assertTrue($em->contains($ph2));
// Delete // Delete
$em->delete($user); $em->delete($user);
$this->assertTrue($em->getUnitOfWork()->isRegisteredRemoved($user)); $this->assertTrue($em->getUnitOfWork()->isRegisteredRemoved($user));
$this->assertTrue($em->getUnitOfWork()->isRegisteredRemoved($ph));
$this->assertTrue($em->getUnitOfWork()->isRegisteredRemoved($ph2));
$em->flush(); $em->flush();
$this->assertFalse($em->getUnitOfWork()->isRegisteredRemoved($user)); $this->assertFalse($em->getUnitOfWork()->isRegisteredRemoved($user));
$this->assertFalse($em->getUnitOfWork()->isRegisteredRemoved($ph));
$this->assertFalse($em->getUnitOfWork()->isRegisteredRemoved($ph2));
} }
public function testMore() { /*public function testMore() {
#echo PHP_EOL . "SECOND" . PHP_EOL;
/*$user = new CmsUser;
$user->name = 'jon';
$user->*/
$ph = new CmsPhonenumber; $ph = new CmsPhonenumber;
$ph->phonenumber = 123456; $ph->phonenumber = 123456;
@ -67,6 +75,6 @@ class BasicCRUDTest extends \Doctrine\Tests\OrmFunctionalTestCase {
$this->_em->save($ph); $this->_em->save($ph);
$this->_em->flush(); $this->_em->flush();
} }*/
} }

View File

@ -53,7 +53,6 @@ class ObjectHydratorTest extends HydrationTest
$queryComponents, $tableAliasMap)); $queryComponents, $tableAliasMap));
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue($result instanceof \Doctrine\ORM\Collection);
$this->assertTrue($result[0] instanceof \Doctrine\Tests\Models\CMS\CmsUser); $this->assertTrue($result[0] instanceof \Doctrine\Tests\Models\CMS\CmsUser);
$this->assertTrue($result[1] instanceof \Doctrine\Tests\Models\CMS\CmsUser); $this->assertTrue($result[1] instanceof \Doctrine\Tests\Models\CMS\CmsUser);
$this->assertEquals(1, $result[0]->id); $this->assertEquals(1, $result[0]->id);
@ -659,7 +658,6 @@ class ObjectHydratorTest extends HydrationTest
$queryComponents, $tableAliasMap)); $queryComponents, $tableAliasMap));
$this->assertEquals(2, count($result)); $this->assertEquals(2, count($result));
$this->assertTrue($result instanceof \Doctrine\ORM\Collection);
$this->assertTrue($result[0] instanceof \Doctrine\Tests\Models\Forum\ForumCategory); $this->assertTrue($result[0] instanceof \Doctrine\Tests\Models\Forum\ForumCategory);
$this->assertTrue($result[1] instanceof \Doctrine\Tests\Models\Forum\ForumCategory); $this->assertTrue($result[1] instanceof \Doctrine\Tests\Models\Forum\ForumCategory);
$this->assertEquals(1, $result[0]->getId()); $this->assertEquals(1, $result[0]->getId());

View File

@ -159,7 +159,7 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
)); ));
// Go // Go
$this->_unitOfWork->computeEntityChangeSets(array($user1, $user2)); $this->_unitOfWork->computeChangeSets(array($user1, $user2));
// Verify // Verify
$user1ChangeSet = $this->_unitOfWork->getEntityChangeSet($user1); $user1ChangeSet = $this->_unitOfWork->getEntityChangeSet($user1);