2007-10-23 01:47:05 +04:00
|
|
|
<?php
|
|
|
|
/*
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* This software consists of voluntary contributions made by many individuals
|
|
|
|
* and is licensed under the LGPL. For more information, see
|
2008-01-23 01:52:53 +03:00
|
|
|
* <http://www.phpdoctrine.org>.
|
2007-10-23 01:47:05 +04:00
|
|
|
*/
|
|
|
|
Doctrine::autoload('Doctrine_Connection_Module');
|
|
|
|
/**
|
|
|
|
* Doctrine_Connection_UnitOfWork
|
|
|
|
*
|
|
|
|
* @package Doctrine
|
|
|
|
* @subpackage Connection
|
|
|
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
2008-02-22 21:11:35 +03:00
|
|
|
* @link www.phpdoctrine.org
|
2007-10-23 01:47:05 +04:00
|
|
|
* @since 1.0
|
|
|
|
* @version $Revision$
|
|
|
|
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
2008-02-24 01:04:39 +03:00
|
|
|
* @author Roman Borschel <roman@code-factory.org>
|
2008-02-13 13:53:07 +03:00
|
|
|
* @todo package:orm. Figure out a useful implementation.
|
2007-10-23 01:47:05 +04:00
|
|
|
*/
|
|
|
|
class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module
|
|
|
|
{
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
|
|
|
* A map of all currently managed entities.
|
|
|
|
*
|
|
|
|
* @var array
|
2008-04-13 00:11:11 +04:00
|
|
|
* @deprecated Only here to keep the saveAll() functionality working. We don't need
|
|
|
|
* this in the future.
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
|
|
|
protected $_managedEntities = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The identity map that holds references to all managed entities that have
|
2008-03-05 14:24:33 +03:00
|
|
|
* an identity. The entities are grouped by their class name.
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
2008-01-05 22:55:56 +03:00
|
|
|
protected $_autoflush = true;
|
2008-02-24 01:04:39 +03:00
|
|
|
|
|
|
|
/**
|
2008-04-13 00:11:11 +04:00
|
|
|
* A list of all new entities.
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2008-04-13 00:11:11 +04:00
|
|
|
protected $_newEntities = array();
|
2008-02-24 01:04:39 +03:00
|
|
|
|
|
|
|
/**
|
2008-04-13 00:11:11 +04:00
|
|
|
* A list of all dirty entities.
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2008-04-13 00:11:11 +04:00
|
|
|
protected $_dirtyEntities = array();
|
2008-02-24 01:04:39 +03:00
|
|
|
|
|
|
|
/**
|
2008-04-13 00:11:11 +04:00
|
|
|
* A list of all removed entities.
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
2008-04-13 00:11:11 +04:00
|
|
|
protected $_removedEntities = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The EntityManager the unit of work belongs to.
|
|
|
|
*/
|
|
|
|
protected $_em;
|
2008-01-05 22:55:56 +03:00
|
|
|
|
2008-02-24 01:04:39 +03:00
|
|
|
/**
|
|
|
|
* The dbal connection used by the unit of work.
|
|
|
|
*
|
|
|
|
* @var Doctrine_Connection
|
2008-04-13 00:11:11 +04:00
|
|
|
* @todo Not needed in the future. Remove.
|
2008-02-24 01:04:39 +03:00
|
|
|
*/
|
|
|
|
protected $_conn;
|
|
|
|
|
|
|
|
/**
|
2008-04-13 00:11:11 +04:00
|
|
|
* Commits the unit of work, executing all operations that have been postponed
|
2008-02-24 01:04:39 +03:00
|
|
|
* up to this point.
|
|
|
|
*
|
|
|
|
*/
|
2008-04-13 00:11:11 +04:00
|
|
|
public function commit()
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2008-04-13 00:11:11 +04:00
|
|
|
$this->_orderCommits();
|
2008-02-28 18:30:55 +03:00
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
$this->_insertNew();
|
|
|
|
$this->_updateDirty();
|
|
|
|
$this->_deleteRemoved();
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
private function _orderCommits()
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2008-04-13 00:11:11 +04:00
|
|
|
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
|
|
|
* Register a new entity.
|
|
|
|
*/
|
|
|
|
public function registerNew(Doctrine_Record $entity)
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2008-04-13 00:11:11 +04:00
|
|
|
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;
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
|
|
|
* Registers a clean entity.
|
|
|
|
*/
|
|
|
|
public function registerClean(Doctrine_Record $entity)
|
2008-01-05 22:55:56 +03:00
|
|
|
{
|
2008-04-13 00:11:11 +04:00
|
|
|
$this->registerIdentity($entity);
|
2008-01-05 22:55:56 +03:00
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
/**
|
|
|
|
* Registers a deleted entity.
|
|
|
|
*/
|
|
|
|
public function registerDeleted(Doctrine_Record $entity)
|
|
|
|
{
|
|
|
|
$this->unregisterIdentity($entity);
|
|
|
|
$this->_removedEntities[$entity->getOid()] = $entity;
|
|
|
|
}
|
|
|
|
|
2007-10-23 01:47:05 +04:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2008-02-13 13:53:07 +03:00
|
|
|
public function buildFlushTree(array $mappers)
|
2007-10-23 01:47:05 +04:00
|
|
|
{
|
|
|
|
$tree = array();
|
2008-02-13 13:53:07 +03:00
|
|
|
foreach ($mappers as $k => $mapper) {
|
2008-02-20 23:54:20 +03:00
|
|
|
if ( ! ($mapper instanceof Doctrine_Mapper)) {
|
2008-02-13 13:53:07 +03:00
|
|
|
$mapper = $this->conn->getMapper($mapper);
|
2007-10-23 01:47:05 +04:00
|
|
|
}
|
2008-02-24 01:04:39 +03:00
|
|
|
$nm = $mapper->getComponentName();
|
2007-10-23 01:47:05 +04:00
|
|
|
|
2008-02-28 18:30:55 +03:00
|
|
|
$index = array_search($nm, $tree);
|
2007-10-23 01:47:05 +04:00
|
|
|
|
|
|
|
if ($index === false) {
|
|
|
|
$tree[] = $nm;
|
|
|
|
$index = max(array_keys($tree));
|
|
|
|
}
|
|
|
|
|
2008-02-13 13:53:07 +03:00
|
|
|
$rels = $mapper->getTable()->getRelations();
|
2007-10-23 01:47:05 +04:00
|
|
|
|
|
|
|
// 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();
|
2008-02-28 18:30:55 +03:00
|
|
|
$index2 = array_search($name, $tree);
|
2007-10-23 01:47:05 +04:00
|
|
|
$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;
|
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
} else if ($rel instanceof Doctrine_Relation_LocalKey) {
|
2007-10-23 01:47:05 +04:00
|
|
|
if ($index2 !== false) {
|
|
|
|
if ($index2 <= $index)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
unset($tree[$index2]);
|
2008-02-28 18:30:55 +03:00
|
|
|
array_splice($tree, $index, 0, $name);
|
2007-10-23 01:47:05 +04:00
|
|
|
} else {
|
|
|
|
array_unshift($tree,$name);
|
|
|
|
$index++;
|
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
} else if ($rel instanceof Doctrine_Relation_Association) {
|
2007-10-23 01:47:05 +04:00
|
|
|
$t = $rel->getAssociationFactory();
|
|
|
|
$n = $t->getComponentName();
|
|
|
|
|
2008-02-28 18:30:55 +03:00
|
|
|
if ($index2 !== false) {
|
2007-10-23 01:47:05 +04:00
|
|
|
unset($tree[$index2]);
|
2008-02-28 18:30:55 +03:00
|
|
|
}
|
2007-10-23 01:47:05 +04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-02-24 01:04:39 +03:00
|
|
|
|
2008-02-28 18:30:55 +03:00
|
|
|
return $tree;
|
2007-10-23 01:47:05 +04:00
|
|
|
}
|
2008-01-05 22:55:56 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* saveAll
|
|
|
|
* persists all the pending records from all tables
|
|
|
|
*
|
|
|
|
* @throws PDOException if something went wrong at database level
|
|
|
|
* @return void
|
2008-02-13 13:53:07 +03:00
|
|
|
* @deprecated
|
2008-01-05 22:55:56 +03:00
|
|
|
*/
|
|
|
|
public function saveAll()
|
|
|
|
{
|
2008-04-13 00:11:11 +04:00
|
|
|
$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();
|
2007-10-23 01:47:05 +04:00
|
|
|
}
|
2007-11-18 19:06:37 +03:00
|
|
|
|
2008-02-28 18:30:55 +03:00
|
|
|
/**
|
|
|
|
* Adds an entity to the pool of managed entities.
|
|
|
|
*
|
|
|
|
*/
|
2008-03-05 14:24:33 +03:00
|
|
|
public function manage(Doctrine_Record $entity)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
|
|
|
$oid = $entity->getOid();
|
|
|
|
if ( ! isset($this->_managedEntities[$oid])) {
|
|
|
|
$this->_managedEntities[$oid] = $entity;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-03-05 14:24:33 +03:00
|
|
|
* Gets a managed entity by it's object id (oid).
|
|
|
|
*
|
|
|
|
* @param integer $oid The object id.
|
2008-02-28 18:30:55 +03:00
|
|
|
* @throws Doctrine_Table_Repository_Exception
|
|
|
|
*/
|
2008-03-05 14:24:33 +03:00
|
|
|
public function getByOid($oid)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
|
|
|
if ( ! isset($this->_managedEntities[$oid])) {
|
2008-03-05 14:24:33 +03:00
|
|
|
throw new Doctrine_Connection_Exception("Unknown object identifier '$oid'.");
|
2008-02-28 18:30:55 +03:00
|
|
|
}
|
|
|
|
return $this->_managedEntities[$oid];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param integer $oid object identifier
|
|
|
|
* @return boolean whether ot not the operation was successful
|
|
|
|
*/
|
2008-03-05 14:24:33 +03:00
|
|
|
public function detach(Doctrine_Record $entity)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
|
|
|
$oid = $entity->getOid();
|
|
|
|
if ( ! isset($this->_managedEntities[$oid])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
unset($this->_managedEntities[$oid]);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-03-05 14:24:33 +03:00
|
|
|
* Detaches all currently managed entities.
|
|
|
|
*
|
|
|
|
* @return integer The number of detached entities.
|
2008-02-28 18:30:55 +03:00
|
|
|
*/
|
2008-03-05 14:24:33 +03:00
|
|
|
public function detachAll()
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
2008-03-05 14:24:33 +03:00
|
|
|
$numDetached = count($this->_managedEntities);
|
|
|
|
$this->_managedEntities = array();
|
|
|
|
return $numDetached;
|
2008-02-28 18:30:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-03-05 14:24:33 +03:00
|
|
|
* 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.
|
2008-02-28 18:30:55 +03:00
|
|
|
*/
|
2008-03-05 14:24:33 +03:00
|
|
|
public function isManaged(Doctrine_Record $entity)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
2008-03-05 14:24:33 +03:00
|
|
|
return isset($this->_managedEntities[$entity->getOid()]);
|
2008-02-28 18:30:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-03-05 14:24:33 +03:00
|
|
|
* Registers an entity in the identity map.
|
2008-02-28 18:30:55 +03:00
|
|
|
*
|
2008-03-05 14:24:33 +03:00
|
|
|
* @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.
|
2008-02-28 18:30:55 +03:00
|
|
|
*/
|
2008-03-05 14:24:33 +03:00
|
|
|
public function registerIdentity(Doctrine_Record $entity)
|
2008-02-28 18:30:55 +03:00
|
|
|
{
|
|
|
|
$id = implode(' ', $entity->identifier());
|
2008-03-05 14:24:33 +03:00
|
|
|
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.");
|
|
|
|
}
|
2008-02-28 18:30:55 +03:00
|
|
|
$className = $entity->getClassMetadata()->getRootClassName();
|
|
|
|
if (isset($this->_identityMap[$className][$id])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$this->_identityMap[$className][$id] = $entity;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
public function clearIdentitiesForEntity($entityName)
|
|
|
|
{
|
|
|
|
$this->_identityMap[$entityName] = array();
|
|
|
|
}
|
|
|
|
|
2008-03-05 14:24:33 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-04-13 00:11:11 +04:00
|
|
|
public function getByIdentity($id, $rootClassName)
|
2008-03-05 14:24:33 +03:00
|
|
|
{
|
2008-04-13 00:11:11 +04:00
|
|
|
return $this->_identityMap[$rootClassName][$id];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function containsIdentity($id, $rootClassName)
|
|
|
|
{
|
|
|
|
return isset($this->_identityMap[$rootClassName][$id]);
|
2008-03-05 14:24:33 +03:00
|
|
|
}
|
|
|
|
|
2007-10-23 01:47:05 +04:00
|
|
|
}
|