2006-09-20 13:46:52 +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
|
|
|
|
* <http://www.phpdoctrine.com>.
|
|
|
|
*/
|
2006-11-16 15:46:23 +03:00
|
|
|
Doctrine::autoload('Doctrine_Connection_Module');
|
2006-09-20 13:46:52 +04:00
|
|
|
/**
|
2006-09-20 15:29:35 +04:00
|
|
|
* Doctrine_Connection_UnitOfWork
|
2006-09-20 13:46:52 +04:00
|
|
|
*
|
2006-11-16 15:46:23 +03:00
|
|
|
* @package Doctrine
|
|
|
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
|
|
|
* @category Object Relational Mapping
|
|
|
|
* @link www.phpdoctrine.com
|
|
|
|
* @since 1.0
|
|
|
|
* @version $Revision$
|
|
|
|
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
|
|
|
*/
|
|
|
|
class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module implements IteratorAggregate, Countable {
|
2006-09-20 13:46:52 +04:00
|
|
|
/**
|
|
|
|
* buildFlushTree
|
|
|
|
* builds a flush tree that is used in transactions
|
2006-10-31 02:00:09 +03:00
|
|
|
*
|
|
|
|
* 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.
|
2006-09-20 13:46:52 +04:00
|
|
|
*
|
2006-10-31 02:00:09 +03:00
|
|
|
* @param array $tables
|
2006-09-20 13:46:52 +04:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function buildFlushTree(array $tables) {
|
|
|
|
$tree = array();
|
|
|
|
foreach($tables as $k => $table) {
|
|
|
|
$k = $k.$table;
|
|
|
|
if( ! ($table instanceof Doctrine_Table))
|
|
|
|
$table = $this->conn->getTable($table);
|
|
|
|
|
|
|
|
$nm = $table->getComponentName();
|
|
|
|
|
|
|
|
$index = array_search($nm,$tree);
|
|
|
|
|
|
|
|
if($index === false) {
|
|
|
|
$tree[] = $nm;
|
|
|
|
$index = max(array_keys($tree));
|
|
|
|
}
|
|
|
|
|
|
|
|
$rels = $table->getRelations();
|
|
|
|
|
|
|
|
// group relations
|
|
|
|
|
|
|
|
foreach($rels as $key => $rel) {
|
2006-09-29 00:57:39 +04:00
|
|
|
if($rel instanceof Doctrine_Relation_ForeignKey) {
|
2006-09-20 13:46:52 +04:00
|
|
|
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;
|
|
|
|
|
2006-09-29 00:57:39 +04:00
|
|
|
if($rel instanceof Doctrine_Relation_ForeignKey) {
|
2006-09-20 13:46:52 +04:00
|
|
|
if($index2 !== false) {
|
|
|
|
if($index2 >= $index)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
unset($tree[$index]);
|
|
|
|
array_splice($tree,$index2,0,$nm);
|
|
|
|
$index = $index2;
|
|
|
|
} else {
|
|
|
|
$tree[] = $name;
|
|
|
|
}
|
|
|
|
|
2006-09-29 00:57:39 +04:00
|
|
|
} elseif($rel instanceof Doctrine_Relation_LocalKey) {
|
2006-09-20 13:46:52 +04:00
|
|
|
if($index2 !== false) {
|
|
|
|
if($index2 <= $index)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
unset($tree[$index2]);
|
|
|
|
array_splice($tree,$index,0,$name);
|
|
|
|
} else {
|
|
|
|
array_unshift($tree,$name);
|
|
|
|
$index++;
|
|
|
|
}
|
2006-09-29 00:57:39 +04:00
|
|
|
} elseif($rel instanceof Doctrine_Relation_Association) {
|
2006-09-20 13:46:52 +04:00
|
|
|
$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 array_values($tree);
|
|
|
|
}
|
2006-10-31 02:00:09 +03:00
|
|
|
/**
|
|
|
|
* saveRelated
|
|
|
|
* saves all related records to $record
|
|
|
|
*
|
2006-10-31 13:42:46 +03:00
|
|
|
* @throws PDOException if something went wrong at database level
|
2006-10-31 02:00:09 +03:00
|
|
|
* @param Doctrine_Record $record
|
|
|
|
*/
|
|
|
|
public function saveRelated(Doctrine_Record $record) {
|
|
|
|
$saveLater = array();
|
|
|
|
foreach($record->getReferences() as $k=>$v) {
|
|
|
|
$fk = $record->getTable()->getRelation($k);
|
|
|
|
if($fk instanceof Doctrine_Relation_ForeignKey ||
|
|
|
|
$fk instanceof Doctrine_Relation_LocalKey) {
|
|
|
|
if($fk->isComposite()) {
|
|
|
|
$local = $fk->getLocal();
|
|
|
|
$foreign = $fk->getForeign();
|
|
|
|
|
|
|
|
if($record->getTable()->hasPrimaryKey($fk->getLocal())) {
|
|
|
|
if( ! $record->exists())
|
|
|
|
$saveLater[$k] = $fk;
|
|
|
|
else
|
|
|
|
$v->save();
|
|
|
|
} else {
|
|
|
|
// ONE-TO-ONE relationship
|
|
|
|
$obj = $record->get($fk->getTable()->getComponentName());
|
|
|
|
|
|
|
|
if($obj->getState() != Doctrine_Record::STATE_TCLEAN)
|
|
|
|
$obj->save();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} elseif($fk instanceof Doctrine_Relation_Association) {
|
|
|
|
$v->save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $saveLater;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* saveAssociations
|
|
|
|
*
|
|
|
|
* this method takes a diff of one-to-many / many-to-many original and
|
|
|
|
* current collections and applies the changes
|
|
|
|
*
|
|
|
|
* for example if original many-to-many related collection has records with
|
|
|
|
* primary keys 1,2 and 3 and the new collection has records with primary keys
|
|
|
|
* 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then
|
|
|
|
* save new associations to 4 and 5
|
|
|
|
*
|
2006-10-31 13:42:46 +03:00
|
|
|
* @throws PDOException if something went wrong at database level
|
2006-10-31 02:00:09 +03:00
|
|
|
* @param Doctrine_Record $record
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function saveAssociations(Doctrine_Record $record) {
|
|
|
|
foreach($record->getTable()->getRelations() as $rel) {
|
|
|
|
$table = $rel->getTable();
|
|
|
|
$alias = $rel->getAlias();
|
2006-09-20 13:46:52 +04:00
|
|
|
|
2006-10-31 02:00:09 +03:00
|
|
|
$rel->processDiff($record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* deletes all related composites
|
|
|
|
* this method is always called internally when a record is deleted
|
|
|
|
*
|
2006-10-31 13:42:46 +03:00
|
|
|
* @throws PDOException if something went wrong at database level
|
2006-10-31 02:00:09 +03:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function deleteComposites(Doctrine_Record $record) {
|
|
|
|
foreach($record->getTable()->getRelations() as $fk) {
|
|
|
|
switch($fk->getType()):
|
|
|
|
case Doctrine_Relation::ONE_COMPOSITE:
|
|
|
|
case Doctrine_Relation::MANY_COMPOSITE:
|
|
|
|
$obj = $record->get($fk->getAlias());
|
|
|
|
$obj->delete();
|
|
|
|
break;
|
|
|
|
endswitch;
|
|
|
|
}
|
|
|
|
}
|
2006-10-31 13:42:46 +03:00
|
|
|
/**
|
|
|
|
* saveAll
|
|
|
|
* persists all the pending records from all tables
|
|
|
|
*
|
|
|
|
* @throws PDOException if something went wrong at database level
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function saveAll() {
|
|
|
|
// get the flush tree
|
|
|
|
$tree = $this->buildFlushTree($this->conn->getTables());
|
|
|
|
|
|
|
|
// save all records
|
|
|
|
foreach($tree as $name) {
|
|
|
|
$table = $this->conn->getTable($name);
|
|
|
|
|
|
|
|
foreach($table->getRepository() as $record) {
|
|
|
|
$this->conn->save($record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// save all associations
|
|
|
|
foreach($tree as $name) {
|
|
|
|
$table = $this->conn->getTable($name);
|
|
|
|
|
|
|
|
foreach($table->getRepository() as $record) {
|
|
|
|
$this->saveAssociations($record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2006-11-30 00:09:02 +03:00
|
|
|
/**
|
|
|
|
* updates the given record
|
|
|
|
*
|
|
|
|
* @param Doctrine_Record $record
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function update(Doctrine_Record $record) {
|
|
|
|
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreUpdate($record);
|
|
|
|
|
|
|
|
$array = $record->getPrepared();
|
|
|
|
|
|
|
|
if(empty($array))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$set = array();
|
|
|
|
foreach($array as $name => $value):
|
|
|
|
$set[] = $name." = ?";
|
|
|
|
|
|
|
|
if($value instanceof Doctrine_Record) {
|
|
|
|
switch($value->getState()):
|
|
|
|
case Doctrine_Record::STATE_TCLEAN:
|
|
|
|
case Doctrine_Record::STATE_TDIRTY:
|
|
|
|
$record->save();
|
|
|
|
default:
|
|
|
|
$array[$name] = $value->getIncremented();
|
|
|
|
$record->set($name, $value->getIncremented());
|
|
|
|
endswitch;
|
|
|
|
}
|
|
|
|
endforeach;
|
|
|
|
|
|
|
|
$params = array_values($array);
|
|
|
|
$id = $record->obtainIdentifier();
|
|
|
|
|
|
|
|
|
|
|
|
if( ! is_array($id))
|
|
|
|
$id = array($id);
|
|
|
|
|
|
|
|
$id = array_values($id);
|
|
|
|
$params = array_merge($params, $id);
|
|
|
|
|
|
|
|
|
2006-12-14 16:26:16 +03:00
|
|
|
$sql = 'UPDATE ' . $record->getTable()->getTableName()
|
|
|
|
. ' SET ' . implode(', ', $set)
|
|
|
|
. ' WHERE ' . implode(' = ? AND ', $record->getTable()->getPrimaryKeys())
|
|
|
|
. ' = ?';
|
2006-11-30 00:09:02 +03:00
|
|
|
|
|
|
|
$stmt = $this->conn->getDBH()->prepare($sql);
|
|
|
|
$stmt->execute($params);
|
|
|
|
|
|
|
|
$record->assignIdentifier(true);
|
|
|
|
|
|
|
|
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onUpdate($record);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* inserts a record into database
|
|
|
|
*
|
|
|
|
* @param Doctrine_Record $record record to be inserted
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function insert(Doctrine_Record $record) {
|
|
|
|
// listen the onPreInsert event
|
|
|
|
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreInsert($record);
|
|
|
|
|
|
|
|
$array = $record->getPrepared();
|
|
|
|
|
|
|
|
if(empty($array))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
$table = $record->getTable();
|
|
|
|
$keys = $table->getPrimaryKeys();
|
|
|
|
|
|
|
|
|
|
|
|
$seq = $record->getTable()->getSequenceName();
|
|
|
|
|
|
|
|
if( ! empty($seq)) {
|
|
|
|
$id = $this->nextId($seq);
|
|
|
|
$name = $record->getTable()->getIdentifier();
|
|
|
|
$array[$name] = $id;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->conn->insert($table->getTableName(), $array);
|
|
|
|
|
|
|
|
if(count($keys) == 1 && $keys[0] == $table->getIdentifier()) {
|
|
|
|
$id = $this->conn->getDBH()->lastInsertID();
|
|
|
|
|
|
|
|
if( ! $id)
|
|
|
|
$id = $table->getMaxIdentifier();
|
|
|
|
|
|
|
|
$record->assignIdentifier($id);
|
|
|
|
} else
|
|
|
|
$record->assignIdentifier(true);
|
|
|
|
|
|
|
|
// listen the onInsert event
|
|
|
|
$table->getAttribute(Doctrine::ATTR_LISTENER)->onInsert($record);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2006-09-20 13:46:52 +04:00
|
|
|
public function getIterator() { }
|
|
|
|
|
|
|
|
public function count() { }
|
|
|
|
}
|