. */ Doctrine::autoload('Doctrine_Connection_Module'); /** * Doctrine_Connection_UnitOfWork * * @package Doctrine * @subpackage Connection * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 * @version $Revision$ * @author Konsta Vesterinen * @author Roman Borschel * @todo package:orm. Figure out a useful implementation. */ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module { /** * A map of all currently managed entities. * * @var array * @deprecated Only here to keep the saveAll() functionality working. We don't need * this in the future. */ protected $_managedEntities = array(); /** * The identity map that holds references to all managed entities that have * an identity. The entities are grouped by their class name. */ protected $_identityMap = array(); /** * Boolean flag that indicates whether the unit of work immediately executes any * database operations or whether these operations are postponed until the * unit of work is flushed/committed. * * @var boolean */ protected $_autoflush = true; /** * A list of all new entities. */ protected $_newEntities = array(); /** * A list of all dirty entities. */ protected $_dirtyEntities = array(); /** * A list of all removed entities. */ protected $_removedEntities = array(); /** * The EntityManager the unit of work belongs to. */ protected $_em; /** * The dbal connection used by the unit of work. * * @var Doctrine_Connection * @todo Not needed in the future. Remove. */ protected $_conn; /** * Commits the unit of work, executing all operations that have been postponed * up to this point. * */ public function commit() { $this->_orderCommits(); $this->_insertNew(); $this->_updateDirty(); $this->_deleteRemoved(); } private function _orderCommits() { } /** * Register a new entity. */ public function registerNew(Doctrine_Record $entity) { if (isset($this->_dirtyEntities[$entity->getOid()])) { throw new Doctrine_Connection_Exception("Dirty object can't be registered as new."); } else if (isset($this->_removedEntities[$entity->getOid()])) { throw new Doctrine_Connection_Exception("Removed object can't be registered as new."); } $this->_newEntities[$entity->getOid()] = $entity; } /** * Registers a clean entity. */ public function registerClean(Doctrine_Record $entity) { $this->registerIdentity($entity); } /** * Registers a dirty entity. */ public function registerDirty(Doctrine_Record $entity) { if (isset($this->_removedEntities[$entity->getOid()])) { throw new Doctrine_Connection_Exception("Removed object can't be registered as dirty."); } else if (isset($this->_newEntities[$entity->getOid()])) { throw new Doctrine_Connection_Exception(""); } $this->_dirtyEntities[$entity->getOid()] = $entity; } /** * Registers a deleted entity. */ public function registerDeleted(Doctrine_Record $entity) { $this->unregisterIdentity($entity); $this->_removedEntities[$entity->getOid()] = $entity; } /** * buildFlushTree * builds a flush tree that is used in transactions * * The returned array has all the initialized components in * 'correct' order. Basically this means that the records of those * components can be saved safely in the order specified by the returned array. * * @param array $tables an array of Doctrine_Table objects or component names * @return array an array of component names in flushing order */ public function buildFlushTree(array $mappers) { $tree = array(); foreach ($mappers as $k => $mapper) { if ( ! ($mapper instanceof Doctrine_Mapper)) { $mapper = $this->conn->getMapper($mapper); } $nm = $mapper->getComponentName(); $index = array_search($nm, $tree); if ($index === false) { $tree[] = $nm; $index = max(array_keys($tree)); } $rels = $mapper->getTable()->getRelations(); // group relations foreach ($rels as $key => $rel) { if ($rel instanceof Doctrine_Relation_ForeignKey) { unset($rels[$key]); array_unshift($rels, $rel); } } foreach ($rels as $rel) { $name = $rel->getTable()->getComponentName(); $index2 = array_search($name, $tree); $type = $rel->getType(); // skip self-referenced relations if ($name === $nm) { continue; } if ($rel instanceof Doctrine_Relation_ForeignKey) { if ($index2 !== false) { if ($index2 >= $index) continue; unset($tree[$index]); array_splice($tree,$index2,0,$nm); $index = $index2; } else { $tree[] = $name; } } else if ($rel instanceof Doctrine_Relation_LocalKey) { if ($index2 !== false) { if ($index2 <= $index) continue; unset($tree[$index2]); array_splice($tree, $index, 0, $name); } else { array_unshift($tree,$name); $index++; } } else if ($rel instanceof Doctrine_Relation_Association) { $t = $rel->getAssociationFactory(); $n = $t->getComponentName(); if ($index2 !== false) { unset($tree[$index2]); } array_splice($tree, $index, 0, $name); $index++; $index3 = array_search($n, $tree); if ($index3 !== false) { if ($index3 >= $index) continue; unset($tree[$index]); array_splice($tree, $index3, 0, $n); $index = $index2; } else { $tree[] = $n; } } } } return $tree; } /** * saveAll * persists all the pending records from all tables * * @throws PDOException if something went wrong at database level * @return void * @deprecated */ public function saveAll() { $this->conn->beginInternalTransaction(); // get the flush tree $tree = $this->buildFlushTree($this->conn->getMappers()); $tree = array_combine($tree, array_fill(0, count($tree), array())); foreach ($this->_managedEntities as $oid => $entity) { $className = $entity->getClassName(); $tree[$className][] = $entity; } // save all records foreach ($tree as $className => $entities) { $mapper = $this->conn->getMapper($className); foreach ($entities as $entity) { $mapper->saveSingleRecord($entity); } } // save all associations foreach ($tree as $className => $entities) { $mapper = $this->conn->getMapper($className); foreach ($entities as $entity) { $mapper->saveAssociations($entity); } } $this->conn->commit(); } /** * Adds an entity to the pool of managed entities. * */ public function manage(Doctrine_Record $entity) { $oid = $entity->getOid(); if ( ! isset($this->_managedEntities[$oid])) { $this->_managedEntities[$oid] = $entity; return true; } return false; } /** * Gets a managed entity by it's object id (oid). * * @param integer $oid The object id. * @throws Doctrine_Table_Repository_Exception */ public function getByOid($oid) { if ( ! isset($this->_managedEntities[$oid])) { throw new Doctrine_Connection_Exception("Unknown object identifier '$oid'."); } return $this->_managedEntities[$oid]; } /** * @param integer $oid object identifier * @return boolean whether ot not the operation was successful */ public function detach(Doctrine_Record $entity) { $oid = $entity->getOid(); if ( ! isset($this->_managedEntities[$oid])) { return false; } unset($this->_managedEntities[$oid]); return true; } /** * Detaches all currently managed entities. * * @return integer The number of detached entities. */ public function detachAll() { $numDetached = count($this->_managedEntities); $this->_managedEntities = array(); return $numDetached; } /** * Checks whether an entity is managed. * * @param Doctrine_Record $entity The entity to check. * @return boolean TRUE if the entity is currently managed by doctrine, FALSE otherwise. */ public function isManaged(Doctrine_Record $entity) { return isset($this->_managedEntities[$entity->getOid()]); } /** * Registers an entity in the identity map. * * @return boolean TRUE if the registration was successful, FALSE if the identity of * the entity in question is already managed. * @throws Doctrine_Connection_Exception If the entity has no (database) identity. */ public function registerIdentity(Doctrine_Record $entity) { $id = implode(' ', $entity->identifier()); if ( ! $id) { throw new Doctrine_Connection_Exception("Entity with oid '" . $entity->getOid() . "' has no database identity and therefore can't be added to the identity map."); } $className = $entity->getClassMetadata()->getRootClassName(); if (isset($this->_identityMap[$className][$id])) { return false; } $this->_identityMap[$className][$id] = $entity; return true; } public function clearIdentitiesForEntity($entityName) { $this->_identityMap[$entityName] = array(); } public function unregisterIdentity(Doctrine_Record $entity) { $id = implode(' ', $entity->identifier()); if ( ! $id) { throw new Doctrine_Connection_Exception("Entity with oid '" . $entity->getOid() . "' has no database identity and therefore can't be removed from the identity map."); } $className = $entity->getClassMetadata()->getRootClassName(); if (isset($this->_identityMap[$className][$id])) { unset($this->_identityMap[$className][$id]); return true; } return false; } public function getByIdentity($id, $rootClassName) { return $this->_identityMap[$rootClassName][$id]; } public function containsIdentity($id, $rootClassName) { return isset($this->_identityMap[$rootClassName][$id]); } }