Db.php 18 KB
Newer Older
doctrine's avatar
doctrine committed
1
<?php
zYne's avatar
zYne committed
2
/*
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *  $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>.
 */
21 22 23 24
/**
 * Doctrine_Db
 * A thin wrapper layer on top of PDO / Doctrine_Adapter
 *
zYne's avatar
zYne committed
25
 * Doctrine_Db provides the following things to underlying database hanlder
26
 *
zYne's avatar
zYne committed
27 28 29 30
 * 1. Event listeners
 *    An easy to use, pluggable eventlistener architecture. Aspects such as
 *    logging, query profiling and caching can be easily implemented through
 *    the use of these listeners
31
 *
zYne's avatar
zYne committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
 * 2. Lazy-connecting
 *    Creating an instance of Doctrine_Db does not connect
 *    to database. Connecting to database is only invoked when actually needed
 *    (for example when query() is being called)
 *
 * 3. Portable error codes
 *    Doctrine_Db_Exception drivers provide portable error code handling.
 *
 * 4. Easy-to-use fetching methods
 *    For convience Doctrine_Db provides methods such as fetchOne(), fetchAssoc() etc.
 *
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @package     Doctrine
 * @category    Object Relational Mapping
 * @link        www.phpdoctrine.com
 * @since       1.0
 * @version     $Revision$
 */
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
class Doctrine_Db implements Countable, IteratorAggregate, Doctrine_Adapter_Interface {
    /**
     * error constants
     */
    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;
    /**
     * @var array $instances        all the instances of this class
     */
    protected static $instances   = array();
doctrine's avatar
doctrine committed
94
    /**
95
     * @var array $isConnected      whether or not a connection has been established
doctrine's avatar
doctrine committed
96
     */
97
    protected $isConnected        = false;
doctrine's avatar
doctrine committed
98
    /**
99
     * @var PDO $dbh                the database handler
doctrine's avatar
doctrine committed
100
     */
101
    protected $dbh;
doctrine's avatar
doctrine committed
102
    /**
103
     * @var array $options
doctrine's avatar
doctrine committed
104
     */
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    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');

doctrine's avatar
doctrine committed
125 126 127

    /**
     * constructor
128 129 130 131
     *
     * @param string $dsn           data source name
     * @param string $user          database username
     * @param string $pass          database password
doctrine's avatar
doctrine committed
132
     */
133 134 135 136 137 138 139 140 141 142
    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();
doctrine's avatar
doctrine committed
143
    }
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163


    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);
doctrine's avatar
doctrine committed
164
        
165
        return $this->options[$name];
doctrine's avatar
doctrine committed
166
    }
167 168 169 170 171 172 173 174 175
    /**
     * addListener
     *
     * @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener
     * @return Doctrine_Db
     */
    public function addListener($listener, $name = null) {
        if( ! ($this->listener instanceof Doctrine_Db_EventListener_Chain))
            $this->listener = new Doctrine_Db_EventListener_Chain();
doctrine's avatar
doctrine committed
176

177 178 179 180 181 182 183 184 185 186 187 188
        $this->listener->add($listener, $name);
        
        return $this;
    }
    /**
     * getListener
     * 
     * @return Doctrine_Db_EventListener_Interface|Doctrine_Overloadable
     */
    public function getListener() {
        return $this->listener;
    }
doctrine's avatar
doctrine committed
189
    /**
190 191 192 193
     * setListener
     *
     * @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener
     * @return Doctrine_Db
doctrine's avatar
doctrine committed
194
     */
195 196 197 198
    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");
doctrine's avatar
doctrine committed
199

200
        $this->listener = $listener;
doctrine's avatar
doctrine committed
201

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
        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);
    }
    /**
     * 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];
doctrine's avatar
doctrine committed
244

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
        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;
doctrine's avatar
doctrine committed
262
        }
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323

		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 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();
doctrine's avatar
doctrine committed
324 325
    }
    /**
326 327 328 329
     * errorInfo
     * Fetch extended error information associated with the last operation on the database handle
     *
     * @return array
doctrine's avatar
doctrine committed
330
     */
331 332 333 334 335 336 337 338 339 340
    public function errorInfo() {
        return $this->dbh->errorInfo();
    }
    /**
     * prepare
     *
     * @param string $statement
     */
    public function prepare($statement) {
        $this->connect();
doctrine's avatar
doctrine committed
341

342
        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::PREPARE, $statement);
doctrine's avatar
doctrine committed
343

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
        $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;
doctrine's avatar
doctrine committed
378 379
    }
    /**
380 381 382 383 384
     * quote
     * quotes a string for use in a query
     *
     * @param string $input
     * @return string
doctrine's avatar
doctrine committed
385
     */
386 387
    public function quote($input) {
        $this->connect();
388

389
        return $this->dbh->quote($input);
doctrine's avatar
doctrine committed
390 391
    }
    /**
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
     * 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
     *
zYne's avatar
zYne committed
416 417
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
418 419 420 421 422
     * @return array
     */
    public function fetchAll($statement, array $params = array()) {
        return $this->query($statement, $params)->fetchAll(PDO::FETCH_ASSOC);
    }
zYne's avatar
zYne committed
423 424 425 426 427 428 429
    /**
     * fetchOne
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return mixed
     */
430 431 432
    public function fetchOne($statement, array $params = array()) {
        return current($this->query($statement, $params)->fetch(PDO::FETCH_NUM));
    }
zYne's avatar
zYne committed
433 434 435 436 437 438 439
    /**
     * fetchRow
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
440 441 442
    public function fetchRow($statement, array $params = array()) {
        return $this->query($statement, $params)->fetch(PDO::FETCH_ASSOC);
    }
zYne's avatar
zYne committed
443 444 445 446 447 448 449
    /**
     * fetchArray
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
450 451 452
    public function fetchArray($statement, array $params = array()) {
        return $this->query($statement, $params)->fetch(PDO::FETCH_NUM);
    }
zYne's avatar
zYne committed
453 454 455 456 457 458 459
    /**
     * fetchColumn
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
460 461 462
    public function fetchColumn($statement, array $params = array()) {
        return $this->query($statement, $params)->fetchAll(PDO::FETCH_COLUMN);
    }
zYne's avatar
zYne committed
463 464 465 466 467 468 469
    /**
     * fetchAssoc
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
470 471 472
    public function fetchAssoc($statement, array $params = array()) {
        return $this->query($statement, $params)->fetchAll(PDO::FETCH_ASSOC);
    }
zYne's avatar
zYne committed
473 474 475 476 477 478 479
    /**
     * fetchBoth
     *
     * @param string $statement         sql query to be executed
     * @param array $params             prepared statement params
     * @return array
     */
480 481 482 483 484 485 486
    public function fetchBoth($statement, array $params = array()) { 
        return $this->query($statement, $params)->fetchAll(PDO::FETCH_BOTH);
    }
    /**
     * lastInsertId
     *
     * @return integer
doctrine's avatar
doctrine committed
487
     */
488 489 490 491
    public function lastInsertId() {
        $this->connect();

        return $this->dbh->lastInsertId();
doctrine's avatar
doctrine committed
492
    }
493 494 495 496 497 498 499 500 501 502 503 504 505
    /**
     * 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);
doctrine's avatar
doctrine committed
506
    
507
        return $return;
doctrine's avatar
doctrine committed
508 509
    }
    /**
510 511 512
     * commits a transaction
     *
     * @return boolean
doctrine's avatar
doctrine committed
513
     */
514 515 516 517 518 519 520 521 522 523
    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;
doctrine's avatar
doctrine committed
524 525
    }
    /**
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
     * 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
     *
doctrine's avatar
doctrine committed
575 576 577
     * @return ArrayIterator
     */
    public function getIterator() {
578 579
        if($this->listener instanceof Doctrine_Db_Profiler)
            return $this->listener;
doctrine's avatar
doctrine committed
580 581
    }
    /**
582
     * count
doctrine's avatar
doctrine committed
583
     * returns the number of executed queries
584
     *
doctrine's avatar
doctrine committed
585 586 587
     * @return integer
     */
    public function count() {
588 589
        return $this->querySequence;
    }  
doctrine's avatar
doctrine committed
590
}