Source for file UnitOfWork.php
Documentation is available at UnitOfWork.php
* $Id: UnitOfWork.php 2197 2007-08-10 20:35:25Z zYne $
* 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>.
* Doctrine_Connection_UnitOfWork
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @category Object Relational Mapping
* @link www.phpdoctrine.com
* @version $Revision: 2197 $
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* 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
foreach ($tables as $k =>
$table) {
$table =
$this->conn->getTable($table, false);
$nm =
$table->getComponentName();
$rels =
$table->getRelations();
foreach ($rels as $key =>
$rel) {
foreach ($rels as $rel) {
$name =
$rel->getTable()->getComponentName();
// skip self-referenced relations
$t =
$rel->getAssociationFactory();
$n =
$t->getComponentName();
return array_values($tree);
* @param Doctrine_Record $record
public function saveGraph(Doctrine_Record $record)
$state =
$record->state();
$conn->beginTransaction();
if ($record->isValid()) {
$record->preSave($event);
$record->getTable()->getRecordListener()->preSave($event);
if ( ! $event->skipOperation) {
$record->getTable()->getRecordListener()->postSave($event);
$record->postSave($event);
$conn->transaction->addInvalid($record);
$state =
$record->state();
foreach ($saveLater as $fk) {
$alias =
$fk->getAlias();
if ($record->hasReference($alias)) {
// save the MANY-TO-MANY associations
* @param Doctrine_Record $record
public function save(Doctrine_Record $record)
$record->preSave($event);
$record->getTable()->getRecordListener()->preSave($event);
if ( ! $event->skipOperation) {
switch ($record->state()) {
$record->getTable()->getRecordListener()->postSave($event);
$record->postSave($event);
* deletes given record and all the related composites
* this operation is isolated by a transaction
* this event can be listened by the onPreDelete and onDelete listeners
* @return boolean true on success, false on failure
public function delete(Doctrine_Record $record)
if ( ! $record->exists()) {
$this->conn->beginTransaction();
$record->preDelete($event);
$record->getTable()->getRecordListener()->preDelete($event);
if ( ! $event->skipOperation) {
$this->conn->transaction->addDelete($record);
$record->getTable()->getRecordListener()->postDelete($event);
$record->postDelete($event);
* saves all related records to $record
* @throws PDOException if something went wrong at database level
* @param Doctrine_Record $record
foreach ($record->getReferences() as $k =>
$v) {
$rel =
$record->getTable()->getRelation($k);
$local =
$rel->getLocal();
$foreign =
$rel->getForeign();
// ONE-TO-ONE relationship
$obj =
$record->get($rel->getAlias());
// Protection against infinite function recursion before attempting to save
* 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
foreach ($record->getReferences() as $k =>
$v) {
$rel =
$record->getTable()->getRelation($k);
$assocTable =
$rel->getAssociationTable();
foreach ($v->getDeleteDiff() as $r) {
$query =
'DELETE FROM ' .
$assocTable->getTableName()
.
' WHERE ' .
$rel->getForeign() .
' = ?'
.
' AND ' .
$rel->getLocal() .
' = ?';
$this->conn->execute($query, array($r->getIncremented(), $record->getIncremented()));
foreach ($v->getInsertDiff() as $r) {
$assocRecord =
$assocTable->create();
$assocRecord->set($rel->getForeign(), $r);
$assocRecord->set($rel->getLocal(), $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
foreach ($record->getTable()->getRelations() as $fk) {
switch ($fk->getType()) {
$obj =
$record->get($fk->getAlias());
* persists all the pending records from all tables
* @throws PDOException if something went wrong at database level
foreach ($tree as $name) {
$table =
$this->conn->getTable($name);
foreach ($table->getRepository() as $record) {
foreach ($tree as $name) {
$table =
$this->conn->getTable($name);
foreach ($table->getRepository() as $record) {
* updates the given record
* @param Doctrine_Record $record record to be updated
* @return boolean whether or not the update was successful
public function update(Doctrine_Record $record)
$record->preUpdate($event);
$record->getTable()->getRecordListener()->preUpdate($event);
if ( ! $event->skipOperation) {
$array =
$record->getPrepared();
foreach ($array as $name =>
$value) {
$set[] =
$value->getSql();
if ( ! $value->exists()) {
$array[$name] =
$value->getIncremented();
$record->set($name, $value->getIncremented());
$id =
$record->identifier();
$sql =
'UPDATE ' .
$this->conn->quoteIdentifier($record->getTable()->getTableName())
.
' WHERE ' .
implode(' = ? AND ', $record->getTable()->getPrimaryKeys())
$stmt =
$this->conn->prepare($sql);
$record->assignIdentifier(true);
$record->getTable()->getRecordListener()->postUpdate($event);
$record->postUpdate($event);
* inserts a record into database
* @param Doctrine_Record $record record to be inserted
public function insert(Doctrine_Record $record)
// listen the onPreInsert event
$record->preInsert($event);
$record->getTable()->getRecordListener()->preInsert($event);
if ( ! $event->skipOperation) {
$array =
$record->getPrepared();
$table =
$record->getTable();
$keys =
$table->getPrimaryKeys();
$seq =
$record->getTable()->sequenceName;
$id =
$this->conn->sequence->nextId($seq);
$name =
$record->getTable()->getIdentifier();
$record->assignIdentifier($id);
$this->conn->insert($table->getTableName(), $array);
if (empty($seq) &&
count($keys) ==
1 &&
$keys[0] ==
$table->getIdentifier() &&
$table->getIdentifierType() !=
Doctrine::IDENTIFIER_NATURAL) {
$seq =
$table->getTableName() .
'_' .
$keys[0];
$id =
$this->conn->sequence->lastInsertId($seq);
$id =
$table->getMaxIdentifier();
$record->assignIdentifier($id);
$record->assignIdentifier(true);
$record->getTable()->addRecord($record);
$record->getTable()->getRecordListener()->postInsert($event);
$record->postInsert($event);