From cbc157fbaead936b1ccf9cff79956f4e633acdc4 Mon Sep 17 00:00:00 2001 From: zYne Date: Sat, 11 Nov 2006 20:00:30 +0000 Subject: [PATCH] replaced the old Doctrine_Db with new one --- lib/Doctrine/Db.php | 543 +++++++++++++++++++++++++++++++++++------ tests/UnitTestCase.php | 2 +- 2 files changed, 471 insertions(+), 74 deletions(-) diff --git a/lib/Doctrine/Db.php b/lib/Doctrine/Db.php index dc27d42fb..079dbdfd3 100644 --- a/lib/Doctrine/Db.php +++ b/lib/Doctrine/Db.php @@ -18,120 +18,517 @@ * and is licensed under the LGPL. For more information, see * . */ -class Doctrine_Db extends PDO implements Countable, IteratorAggregate { + +/** + * Doctrine_Db + * A thin wrapper layer on top of PDO / Doctrine_Adapter + * + * The purpose of this class is to provide an easy to use, pluggable eventlistener + * architecture to database handler object + * + * Aspects such as logging, query profiling and caching can be easily implemented through + * the use of these listeners + * + * @author Konsta Vesterinen + * @license LGPL + * @package Doctrine + */ +class Doctrine_Db implements Countable, IteratorAggregate, Doctrine_Adapter_Interface { /** - * default DSN + * error constants */ - const DSN = "mysql://root:dc34@localhost/test"; + const ERR = -1; + const ERR_SYNTAX = -2; + const ERR_CONSTRAINT = -3; + const ERR_NOT_FOUND = -4; + const ERR_ALREADY_EXISTS = -5; + const ERR_UNSUPPORTED = -6; + const ERR_MISMATCH = -7; + const ERR_INVALID = -8; + const ERR_NOT_CAPABLE = -9; + const ERR_TRUNCATED = -10; + const ERR_INVALID_NUMBER = -11; + const ERR_INVALID_DATE = -12; + const ERR_DIVZERO = -13; + const ERR_NODBSELECTED = -14; + const ERR_CANNOT_CREATE = -15; + const ERR_CANNOT_DELETE = -16; + const ERR_CANNOT_DROP = -17; + const ERR_NOSUCHTABLE = -18; + const ERR_NOSUCHFIELD = -19; + const ERR_NEED_MORE_DATA = -20; + const ERR_NOT_LOCKED = -21; + const ERR_VALUE_COUNT_ON_ROW = -22; + const ERR_INVALID_DSN = -23; + const ERR_CONNECT_FAILED = -24; + const ERR_EXTENSION_NOT_FOUND = -25; + const ERR_NOSUCHDB = -26; + const ERR_ACCESS_VIOLATION = -27; + const ERR_CANNOT_REPLACE = -28; + const ERR_CONSTRAINT_NOT_NULL = -29; + const ERR_DEADLOCK = -30; + const ERR_CANNOT_ALTER = -31; + const ERR_MANAGER = -32; + const ERR_MANAGER_PARSE = -33; + const ERR_LOADMODULE = -34; + const ERR_INSUFFICIENT_DATA = -35; /** - * executed queries + * @var array $instances all the instances of this class */ - private $queries = array(); + protected static $instances = array(); /** - * execution times of the executed queries + * @var array $isConnected whether or not a connection has been established */ - private $exectimes = array(); + protected $isConnected = false; + /** + * @var PDO $dbh the database handler + */ + protected $dbh; + /** + * @var array $options + */ + protected $options = array('dsn' => null, + 'username' => null, + 'password' => null, + ); + /** + * @var Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener + * listener for listening events + */ + protected $listener; + /** + * @var integer $querySequence + */ + protected $querySequence = 0; + + private static $driverMap = array('oracle' => 'oci8', + 'postgres' => 'pgsql', + 'oci' => 'oci8', + 'sqlite2' => 'sqlite', + 'sqlite3' => 'sqlite'); + /** * constructor - * @param string $dsn - * @param string $username - * @param string $password + * + * @param string $dsn data source name + * @param string $user database username + * @param string $pass database password */ - public function __construct($dsn,$username,$password) { - parent::__construct($dsn,$username,$password); - $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array("Doctrine_DbStatement",array($this))); + public function __construct($dsn, $user, $pass) { + if( ! isset($user)) { + $a = self::parseDSN($dsn); + + extract($a); + } + $this->options['dsn'] = $dsn; + $this->options['username'] = $user; + $this->options['password'] = $pass; + $this->listener = new Doctrine_Db_EventListener(); } - - - public static function getConn($dsn,$username = null, $password = null) { - static $instance; + + + public function nextQuerySequence() { + return ++$this->querySequence; + } + /** + * getQuerySequence + */ + public function getQuerySequence() { + return $this->querySequence; + } + /** + * getDBH + */ + public function getDBH() { + return $this->dbh; + } + public function getOption($name) { + if( ! array_key_exists($name, $this->options)) + throw new Doctrine_Db_Exception('Unknown option ' . $name); - if( ! isset($instance)) { - $instance = new Doctrine_Db($dsn,$username,$password); - } - return $instance; + return $this->options[$name]; } - /** - * @param string $dsn PEAR::DB like DSN - * format: schema://user:password@address/dbname + * addListener + * + * @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener + * @return Doctrine_Db */ - public static function getConnection($dsn = null) { - static $instance = array(); - $md5 = md5($dsn); + public function addListener($listener, $name = null) { + if( ! ($this->listener instanceof Doctrine_Db_EventListener_Chain)) + $this->listener = new Doctrine_Db_EventListener_Chain(); - if( ! isset($instance[$md5])) { - if( ! isset($dsn)) { - $a = parse_url(self::DSN); - } else { - $a = parse_url($dsn); - } - $e = array(); - - $e[0] = $a["scheme"].":host=".$a["host"].";dbname=".substr($a["path"],1); - $e[1] = $a["user"]; - $e[2] = $a["pass"]; - - $instance[$md5] = new Doctrine_Db($e[0],$e[1],$e[2]); - } - return $instance[$md5]; + $this->listener->add($listener, $name); + + return $this; } /** - * @param string $query query to be executed + * getListener + * + * @return Doctrine_Db_EventListener_Interface|Doctrine_Overloadable */ - public function query($query) { - try { - $this->queries[] = $query; - $time = microtime(); - - $stmt = parent::query($query); - - $this->exectimes[] = (microtime() - $time); - return $stmt; - } catch(PDOException $e) { - throw $e; - } + public function getListener() { + return $this->listener; } /** - * @param string $query query to be prepared + * setListener + * + * @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener + * @return Doctrine_Db */ - public function prepare($query) { - $this->queries[] = $query; + public function setListener($listener) { + if( ! ($listener instanceof Doctrine_Db_EventListener_Interface) && + ! ($listener instanceof Doctrine_Overloadable)) + throw new Doctrine_Db_Exception("Couldn't set eventlistener for database handler. EventListeners should implement either Doctrine_Db_EventListener_Interface or Doctrine_Overloadable"); - return parent::prepare($query); + $this->listener = $listener; + + return $this; + } + + /** + * connect + * connects into database + * + * @return boolean + */ + public function connect() { + if($this->isConnected) + return false; + + $this->dbh = new PDO($this->options['dsn'], $this->options['username'], $this->options['password']); + $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->dbh->setAttribute(PDO::ATTR_STATEMENT_CLASS, array("Doctrine_Db_Statement", array($this))); + $this->isConnected = true; + return true; + } + + /** + * getConnection + * + * @param string $dsn PEAR::DB like DSN or PDO like DSN + * format for PEAR::DB like DSN: schema://user:password@address/dbname + * + * @return + */ + public static function getConnection($dsn = null, $username = null, $password = null) { + return new self($dsn, $username, $password); } /** - * @param string $time exectime of the last executed query + * driverName + * converts a driver name like (oracle) to appropriate PDO + * driver name (oci8 in the case of oracle) + * + * @param string $name + * @return string + */ + public static function driverName($name) { + if(isset(self::$driverMap[$name])) + return self::$driverMap[$name]; + + return $name; + } + /** + * parseDSN + * + * @param string $dsn + * @return array Parsed contents of DSN + */ + function parseDSN($dsn) { + // silence any warnings + $parts = @parse_url($dsn); + + $names = array('scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment'); + + foreach($names as $name) { + if( ! isset($parts[$name])) + $parts[$name] = null; + } + + if(count($parts) == 0 || ! isset($parts['scheme'])) + throw new Doctrine_Db_Exception('Empty data source name'); + + $drivers = self::getAvailableDrivers(); + + $parts['scheme'] = self::driverName($parts['scheme']); + + if( ! in_array($parts['scheme'], $drivers)) + throw new Doctrine_Db_Exception('Driver '.$parts['scheme'].' not availible or extension not loaded'); + + switch($parts['scheme']) { + case 'sqlite': + if(isset($parts['host']) && $parts['host'] == ':memory') { + $parts['database'] = ':memory:'; + $parts['dsn'] = 'sqlite::memory:'; + } + + break; + case 'mysql': + case 'informix': + case 'oci8': + case 'mssql': + case 'firebird': + case 'pgsql': + case 'odbc': + if( ! isset($parts['path']) || $parts['path'] == '/') + throw new Doctrine_Db_Exception('No database availible in data source name'); + + if(isset($parts['path'])) + $parts['database'] = substr($parts['path'], 1); + + if( ! isset($parts['host'])) + throw new Doctrine_Db_Exception('No hostname set in data source name'); + + $parts['dsn'] = $parts["scheme"].":host=".$parts["host"].";dbname=".$parts["database"]; + break; + default: + throw new Doctrine_Db_Exception('Unknown driver '.$parts['scheme']); + } + + return $parts; + } + /** + * clear + * clears all instances from the memory + * * @return void */ - public function addExecTime($time) { - $this->exectimes[] = $time; + public static function clear() { + self::$instances = array(); + } + + /** + * errorCode + * Fetch the SQLSTATE associated with the last operation on the database handle + * + * @return integer + */ + public function errorCode() { + return $this->dbh->errorCode(); + } + /** + * errorInfo + * Fetch extended error information associated with the last operation on the database handle + * + * @return array + */ + public function errorInfo() { + return $this->dbh->errorInfo(); + } + /** + * prepare + * + * @param string $statement + */ + public function prepare($statement) { + $this->connect(); + + $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::PREPARE, $statement); + + $this->listener->onPrePrepare($event); + + $stmt = $this->dbh->prepare($statement); + + $this->listener->onPrepare($event); + + $this->querySequence++; + + return $stmt; + } + /** + * query + * + * @param string $statement + * @param array $params + * @return Doctrine_Db_Statement|boolean + */ + public function query($statement, array $params = array()) { + $this->connect(); + + $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::QUERY, $statement); + + $this->listener->onPreQuery($event); + + if( ! empty($params)) + $stmt = $this->dbh->query($statement)->execute($params); + else + $stmt = $this->dbh->query($statement); + + $this->listener->onQuery($event); + + $this->querySequence++; + + return $stmt; + } + /** + * quote + * quotes a string for use in a query + * + * @param string $input + * @return string + */ + public function quote($input) { + $this->connect(); + + return $this->dbh->quote($input); + } + /** + * exec + * executes an SQL statement and returns the number of affected rows + * + * @param string $statement + * @return integer + */ + public function exec($statement) { + $this->connect(); + + $args = func_get_args(); + + $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::EXEC, $statement); + + $this->listener->onPreExec($event); + + $rows = $this->dbh->exec($statement); + + $this->listener->onExec($event); + + return $rows; + } + /** + * fetchAll + * + * @return array + */ + public function fetchAll($statement, array $params = array()) { + return $this->query($statement, $params)->fetchAll(PDO::FETCH_ASSOC); + } + + public function fetchOne($statement, array $params = array()) { + return current($this->query($statement, $params)->fetch(PDO::FETCH_NUM)); } - public function getExecTimes() { - return $this->exectimes; + public function fetchRow($statement, array $params = array()) { + return $this->query($statement, $params)->fetch(PDO::FETCH_ASSOC); + } + + public function fetchArray($statement, array $params = array()) { + return $this->query($statement, $params)->fetch(PDO::FETCH_NUM); + } + public function fetchColumn($statement, array $params = array()) { + return $this->query($statement, $params)->fetchAll(PDO::FETCH_COLUMN); + } + public function fetchAssoc($statement, array $params = array()) { + return $this->query($statement, $params)->fetchAll(PDO::FETCH_ASSOC); + } + public function fetchBoth($statement, array $params = array()) { + return $this->query($statement, $params)->fetchAll(PDO::FETCH_BOTH); } /** - * @return array an array of executed queries + * lastInsertId + * + * @return integer */ - public function getQueries() { - return $this->queries; + public function lastInsertId() { + $this->connect(); + + return $this->dbh->lastInsertId(); } /** + * begins a transaction + * + * @return boolean + */ + public function beginTransaction() { + $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::BEGIN); + + $this->listener->onPreBeginTransaction($event); + + $return = $this->dbh->beginTransaction(); + + $this->listener->onBeginTransaction($event); + + return $return; + } + /** + * commits a transaction + * + * @return boolean + */ + public function commit() { + $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::COMMIT); + + $this->listener->onPreCommit($event); + + $return = $this->dbh->commit(); + + $this->listener->onCommit($event); + + return $return; + } + /** + * rollBack + * + * @return boolean + */ + public function rollBack() { + $this->connect(); + + $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::ROLLBACK); + + $this->listener->onPreRollback($event); + + $this->dbh->rollBack(); + + $this->listener->onRollback($event); + } + /** + * getAttribute + * retrieves a database connection attribute + * + * @param integer $attribute + * @return mixed + */ + public function getAttribute($attribute) { + $this->connect(); + + return $this->dbh->getAttribute($attribute); + } + /** + * returns an array of available PDO drivers + */ + public static function getAvailableDrivers() { + return PDO::getAvailableDrivers(); + } + /** + * setAttribute + * sets an attribute + * + * @param integer $attribute + * @param mixed $value + * @return boolean + */ + public function setAttribute($attribute, $value) { + $this->connect(); + + $this->dbh->setAttribute($attribute, $value); + } + /** + * getIterator + * * @return ArrayIterator */ public function getIterator() { - return new ArrayIterator($this->queries); + if($this->listener instanceof Doctrine_Db_Profiler) + return $this->listener; } /** + * count * returns the number of executed queries + * * @return integer */ public function count() { - return count($this->queries); - } - + return $this->querySequence; + } } - diff --git a/tests/UnitTestCase.php b/tests/UnitTestCase.php index 347239448..08802e15c 100644 --- a/tests/UnitTestCase.php +++ b/tests/UnitTestCase.php @@ -71,7 +71,7 @@ class Doctrine_UnitTestCase extends UnitTestCase { $this->manager->setAttribute(Doctrine::ATTR_LISTENER, $this->listener); } else { //$this->dbh = Doctrine_Db::getConnection(); - $this->dbh = Doctrine_Db2::getConnection("sqlite::memory:"); + $this->dbh = Doctrine_Db::getConnection("sqlite::memory:"); //$this->dbh = new PDO("sqlite::memory:"); $this->connection = $this->manager->openConnection($this->dbh);