1
0
mirror of synced 2024-12-13 14:56:01 +03:00
doctrine2/lib/Doctrine/Connection.php

645 lines
20 KiB
PHP

<?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>.
*/
/**
* Doctrine_Connection
*
* @package Doctrine ORM
* @url www.phpdoctrine.com
* @license LGPL
*/
abstract class Doctrine_Connection extends Doctrine_Configurable implements Countable, IteratorAggregate {
/**
* @var $dbh the database handler
*/
private $dbh;
/**
* @var Doctrine_Transaction $transaction the transaction object
*/
private $transaction;
/**
* @var Doctrine_UnitOfWork $unitOfWork the unit of work object
*/
private $unitOfWork;
/**
* @var array $tables an array containing all the initialized Doctrine_Table objects
* keys representing Doctrine_Table component names and values as Doctrine_Table objects
*/
protected $tables = array();
/**
* @var Doctrine_DataDict $dataDict
*/
private $dataDict;
/**
* the constructor
*
* @param Doctrine_Manager $manager the manager object
* @param PDO $pdo the database handler
*/
public function __construct(Doctrine_Manager $manager,PDO $pdo) {
$this->dbh = $pdo;
$this->transaction = new Doctrine_Connection_Transaction($this);
$this->unitOfWork = new Doctrine_Connection_UnitOfWork($this);
$this->setParent($manager);
$this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this);
}
/**
* quoteIdentifier
*
* @param string $identifier identifier to be quoted
* @return string modified identifier
*/
public function quoteIdentifier($identifier) {
return $identifier;
}
/**
* getUnitOfWork
*
* returns the unit of work object
*
* @return Doctrine_UnitOfWork
*/
public function getUnitOfWork() {
return $this->unitOfWork;
}
/**
* getTransaction
*
* returns the current transaction object
*
* @return Doctrine_Transaction
*/
public function getTransaction() {
return $this->transaction;
}
/**
* returns the manager that created this connection
*
* @return Doctrine_Manager
*/
public function getManager() {
return $this->getParent();
}
/**
* returns the database handler of which this connection uses
*
* @return PDO the database handler
*/
public function getDBH() {
return $this->dbh;
}
/**
* returns a datadict object
*
* @return Doctrine_DataDict
*/
public function getDataDict() {
if(isset($this->dataDict))
return $this->dataDict;
$driver = $this->dbh->getAttribute(PDO::ATTR_DRIVER_NAME);
switch($driver) {
case "mysql":
$this->dataDict = new Doctrine_DataDict_Mysql($this);
break;
case "sqlite":
case "sqlite2":
$this->dataDict = new Doctrine_DataDict_Sqlite($this);
break;
case "pgsql":
$this->dataDict = new Doctrine_DataDict_Pgsql($this);
break;
case "oci":
case "oci8":
$this->dataDict = new Doctrine_DataDict_Oracle($this);
break;
case "mssql":
$this->dataDict = new Doctrine_DataDict_Mssql($this);
break;
default:
throw new Doctrine_Connection_Exception("No datadict driver availible for ".$driver);
}
return $this->dataDict;
}
/**
* Set the transacton isolation level.
* (implemented by the connection drivers)
*
* @param string standard isolation level
* READ UNCOMMITTED (allows dirty reads)
* READ COMMITTED (prevents dirty reads)
* REPEATABLE READ (prevents nonrepeatable reads)
* SERIALIZABLE (prevents phantom reads)
*
* @throws PDOException if something fails at the PDO level
* @return void
*/
public function setTransactionIsolation($isolation) {
throw new Doctrine_Connection_Exception('Transaction isolation levels not supported by this database driver.');
}
/**
* getRegexpOperator
* returns the regular expression operator
* (implemented by the connection drivers)
*
* @return string
*/
public function getRegexpOperator() {
throw new Doctrine_Connection_Exception('Regular expression operator is not supported by this database driver.');
}
/**
* getTransactionIsolation
*
* @return string returns the current session transaction isolation level
*/
public function getTransactionIsolation() {
throw new Doctrine_Connection_Exception('Fetching transaction isolation level not supported by this database driver.');
}
/**
* query
* queries the database with Doctrine Query Language
*
* @param string $query DQL query
* @param array $params query parameters
*/
final public function query($query,array $params = array()) {
$parser = new Doctrine_Query($this);
return $parser->query($query, $params);
}
/**
* queries the database with limit and offset
* added to the query and returns a PDOStatement object
*
* @param string $query
* @param integer $limit
* @param integer $offset
* @return PDOStatement
*/
public function select($query,$limit = 0,$offset = 0) {
if($limit > 0 || $offset > 0)
$query = $this->modifyLimitQuery($query,$limit,$offset);
return $this->dbh->query($query);
}
/**
* @param string $query sql query
* @param array $params query parameters
*
* @return PDOStatement
*/
public function execute($query, array $params = array()) {
if( ! empty($params)) {
$stmt = $this->dbh->prepare($query);
$stmt->execute($params);
return $stmt;
} else {
return $this->dbh->query($query);
}
}
/**
* whether or not this connection has table $name initialized
*
* @param $mixed $name
* @return boolean
*/
public function hasTable($name) {
return isset($this->tables[$name]);
}
/**
* returns a table object for given component name
*
* @param string $name component name
* @return object Doctrine_Table
*/
public function getTable($name) {
if(isset($this->tables[$name]))
return $this->tables[$name];
$class = $name."Table";
if(class_exists($class) && in_array("Doctrine_Table", class_parents($class))) {
return new $class($name, $this);
} else {
return new Doctrine_Table($name, $this);
}
}
/**
* returns an array of all initialized tables
*
* @return array
*/
public function getTables() {
return $this->tables;
}
/**
* returns an iterator that iterators through all
* initialized table objects
*
* @return ArrayIterator
*/
public function getIterator() {
return new ArrayIterator($this->tables);
}
/**
* returns the count of initialized table objects
*
* @return integer
*/
public function count() {
return count($this->tables);
}
/**
* addTable
* adds a Doctrine_Table object into connection registry
*
* @param $objTable a Doctrine_Table object to be added into registry
* @return boolean
*/
public function addTable(Doctrine_Table $objTable) {
$name = $objTable->getComponentName();
if(isset($this->tables[$name]))
return false;
$this->tables[$name] = $objTable;
return true;
}
/**
* create
* creates a record
*
* create creates a record
* @param string $name component name
* @return Doctrine_Record Doctrine_Record object
*/
public function create($name) {
return $this->getTable($name)->create();
}
/**
* flush
* saves all the records from all tables
* this operation is isolated using a transaction
*
* @return void
*/
public function flush() {
$this->beginTransaction();
$this->saveAll();
$this->commit();
}
/**
* saveAll
* persists all the records from all tables
*
* @return void
*/
private function saveAll() {
$tree = $this->unitOfWork->buildFlushTree($this->tables);
foreach($tree as $name) {
$table = $this->tables[$name];
foreach($table->getRepository() as $record) {
$this->save($record);
}
}
foreach($tree as $name) {
$table = $this->tables[$name];
foreach($table->getRepository() as $record) {
$record->saveAssociations();
}
}
}
/**
* clear
* clears all repositories
*
* @return void
*/
public function clear() {
foreach($this->tables as $k => $table) {
$table->getRepository()->evictAll();
$table->clear();
}
}
/**
* evictTables
* evicts all tables
*
* @return void
*/
public function evictTables() {
$this->tables = array();
}
/**
* close
* closes the connection
*
* @return void
*/
public function close() {
$this->getAttribute(Doctrine::ATTR_LISTENER)->onPreClose($this);
$this->clear();
$this->state = Doctrine_Connection::STATE_CLOSED;
$this->getAttribute(Doctrine::ATTR_LISTENER)->onClose($this);
}
/**
* get the current transaction nesting level
*
* @return integer
*/
public function getTransactionLevel() {
return $this->transaction->getTransactionLevel();
}
/**
* beginTransaction
* starts a new transaction
* @return void
*/
public function beginTransaction() {
$this->transaction->beginTransaction();
}
/**
* commits the current transaction
* if lockmode is optimistic this method starts a transaction
* and commits it instantly
*
* @return void
*/
public function commit() {
$this->transaction->commit();
}
/**
* rollback
* rolls back all transactions
*
* this method also listens to onPreTransactionRollback and onTransactionRollback
* eventlisteners
*
* @return void
*/
public function rollback() {
$this->transaction->rollback();
}
/**
* returns maximum identifier values
*
* @param array $names an array of component names
* @return array
*/
public function getMaximumValues(array $names) {
$values = array();
foreach($names as $name) {
$table = $this->tables[$name];
$keys = $table->getPrimaryKeys();
$tablename = $table->getTableName();
if(count($keys) == 1 && $keys[0] == $table->getIdentifier()) {
// record uses auto_increment column
$sql = "SELECT MAX(".$table->getIdentifier().") FROM ".$tablename;
$stmt = $this->dbh->query($sql);
$data = $stmt->fetch(PDO::FETCH_NUM);
$values[$tablename] = $data[0];
$stmt->closeCursor();
}
}
return $values;
}
/**
* saves a collection
*
* @param Doctrine_Collection $coll
* @return void
*/
public function saveCollection(Doctrine_Collection $coll) {
$this->beginTransaction();
foreach($coll as $key=>$record):
$record->save();
endforeach;
$this->commit();
}
/**
* deletes all records from collection
*
* @param Doctrine_Collection $coll
* @return void
*/
public function deleteCollection(Doctrine_Collection $coll) {
$this->beginTransaction();
foreach($coll as $k=>$record) {
$record->delete();
}
$this->commit();
}
/**
* saves the given record
*
* @param Doctrine_Record $record
* @return void
*/
public function save(Doctrine_Record $record) {
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record);
switch($record->getState()):
case Doctrine_Record::STATE_TDIRTY:
$this->transaction->insert($record);
break;
case Doctrine_Record::STATE_DIRTY:
case Doctrine_Record::STATE_PROXY:
$this->transaction->update($record);
break;
case Doctrine_Record::STATE_CLEAN:
case Doctrine_Record::STATE_TCLEAN:
// do nothing
break;
endswitch;
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record);
}
/**
* saves all related records to $record
*
* @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;
}
/**
* deletes all related composites
* this method is always called internally when a record is deleted
*
* @return void
*/
final 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;
}
}
/**
* saveAssociations
* save the associations of many-to-many relations
* this method also deletes associations that do not exist anymore
* @return void
*/
final public function saveAssociations(Doctrine_Record $record) {
foreach($record->getTable()->table->getRelations() as $rel):
$table = $rel->getTable();
$name = $table->getComponentName();
$alias = $this->table->getAlias($name);
if($rel instanceof Doctrine_Relation_Association) {
$asf = $rel->getAssociationFactory();
if($record->hasReference($alias)) {
$new = $record->getReference($alias);
if( ! $this->hasOriginalsFor($alias))
$record->loadReference($alias);
$operations = Doctrine_Relation::getDeleteOperations($this->originals[$alias],$new);
foreach($operations as $r) {
$query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?"
." AND ".$fk->getLocal()." = ?";
$this->table->getConnection()->execute($query, array($r->getIncremented(),$record->getIncremented()));
}
$operations = Doctrine_Relation::getInsertOperations($record->obtainOriginals($alias),$new);
foreach($operations as $r) {
$reldao = $asf->create();
$reldao->set($fk->getForeign(),$r);
$reldao->set($fk->getLocal(),$this);
$reldao->save();
}
$record->assignOriginals($alias, clone $this->references[$alias]);
}
} elseif($fk instanceof Doctrine_Relation_ForeignKey ||
$fk instanceof Doctrine_Relation_LocalKey) {
if($fk->isOneToOne()) {
if($record->obtainOriginals($alias) && $record->obtainOriginals($alias)->obtainIdentifier() != $this->references[$alias]->obtainIdentifier())
$record->obtainOriginals($alias)->delete();
} else {
if(isset($this->references[$alias])) {
$new = $this->references[$alias];
if( ! isset($this->originals[$alias]))
$record->loadReference($alias);
$operations = Doctrine_Relation::getDeleteOperations($this->originals[$alias], $new);
foreach($operations as $r) {
$r->delete();
}
$record->assignOriginals($alias, clone $this->references[$alias]);
}
}
}
endforeach;
}
/**
* deletes this data access object 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
*/
final public function delete(Doctrine_Record $record) {
if( ! $record->exists())
return false;
$this->beginTransaction();
$record->getTable()->getListener()->onPreDelete($record);
$this->deleteComposites($record);
$this->transaction->addDelete($record);
$record->getTable()->getListener()->onDelete($record);
$this->commit();
return true;
}
/**
* returns a string representation of this object
* @return string
*/
public function __toString() {
return Doctrine_Lib::getConnectionAsString($this);
}
}