. */ Doctrine::autoload('Doctrine_Connection_Module'); /** * Doctrine_Connection_UnitOfWork * * @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 */ class Doctrine_Connection_UnitOfWork extends Doctrine_Connection_Module implements IteratorAggregate, Countable { /** * 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 * @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) { 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; } } elseif($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++; } } elseif($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 array_values($tree); } /** * saveRelated * saves all related records to $record * * @throws PDOException if something went wrong at database level * @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 * * @throws PDOException if something went wrong at database level * @param Doctrine_Record $record * @return void */ public function saveAssociations(Doctrine_Record $record) { foreach($record->getTable()->getRelations() as $rel) { $table = $rel->getTable(); $alias = $rel->getAlias(); $rel->processDiff($record); } } /** * deletes all related composites * this method is always called internally when a record is deleted * * @throws PDOException if something went wrong at database level * @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; } } /** * 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); } } } /** * 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); $sql = "UPDATE ".$record->getTable()->getTableName()." SET ".implode(", ",$set)." WHERE ".implode(" = ? AND ",$record->getTable()->getPrimaryKeys())." = ?"; $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; } public function getIterator() { } public function count() { } }