DB.php 15.8 KB
Newer Older
zYne's avatar
zYne committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<?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>.
 */

/**
pookey's avatar
pookey committed
23
 * Doctrine_Db
zYne's avatar
zYne committed
24 25 26 27 28 29
 * A thin layer on top of PDO
 *
 * @author      Konsta Vesterinen
 * @license     LGPL
 * @package     Doctrine
 */
pookey's avatar
pookey committed
30
class Doctrine_Db2 implements Countable, IteratorAggregate, Doctrine_Adapter_Interface {
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    /**
     * 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;
zYne's avatar
zYne committed
69 70 71 72 73 74 75 76
    /**
     * @var array $instances        all the instances of this class
     */
    protected static $instances   = array();
    /**
     * @var array $isConnected      whether or not a connection has been established
     */
    protected $isConnected        = false;
zYne's avatar
zYne committed
77
    /**
zYne's avatar
zYne committed
78 79
     * @var PDO $dbh                the database handler
     */
zYne's avatar
zYne committed
80 81
    protected $dbh;
    /**
zYne's avatar
zYne committed
82 83 84 85 86 87 88
     * @var array $options
     */
    protected $options            = array('dsn'      => null,
                                          'username' => null,
                                          'password' => null,
                                          );
    /**
pookey's avatar
pookey committed
89
     * @var Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener     
zYne's avatar
zYne committed
90
     *                              listener for listening events
zYne's avatar
zYne committed
91 92
     */
    protected $listener;
93 94 95 96 97 98 99 100 101 102
    /**
     * @var integer $querySequence
     */
    protected $querySequence  = 0;

    private static $driverMap = array('oracle'     => 'oci8',
                                      'postgres'   => 'pgsql',
                                      'oci'        => 'oci8',
                                      'sqlite2'    => 'sqlite',
                                      'sqlite3'    => 'sqlite');
zYne's avatar
zYne committed
103

zYne's avatar
zYne committed
104 105 106 107 108

    /**
     * constructor
     *
     * @param string $dsn           data source name
zYne's avatar
zYne committed
109 110
     * @param string $user          database username
     * @param string $pass          database password
zYne's avatar
zYne committed
111
     */
zYne's avatar
zYne committed
112 113 114 115 116 117 118 119 120
    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;
pookey's avatar
pookey committed
121
        $this->listener = new Doctrine_Db_EventListener();
zYne's avatar
zYne committed
122
    }
123

zYne's avatar
zYne committed
124 125 126 127

    public function nextQuerySequence() {
        return ++$this->querySequence;
    }
128 129 130 131 132 133
    /**
     * getQuerySequence
     */
    public function getQuerySequence() {
        return $this->querySequence;
    }
zYne's avatar
zYne committed
134 135 136 137 138 139
    /**
     * getDBH
     */
    public function getDBH() {
        return $this->dbh;
    }
zYne's avatar
zYne committed
140 141 142 143 144
    public function getOption($name) {
        if( ! array_key_exists($name, $this->options))
            throw new Doctrine_Db_Exception('Unknown option ' . $name);
        
        return $this->options[$name];
zYne's avatar
zYne committed
145
    }
zYne's avatar
zYne committed
146 147 148
    /**
     * addListener
     *
pookey's avatar
pookey committed
149 150
     * @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener
     * @return Doctrine_Db
zYne's avatar
zYne committed
151 152
     */
    public function addListener($listener, $name = null) {
pookey's avatar
pookey committed
153 154
        if( ! ($this->listener instanceof Doctrine_Db_EventListener_Chain))
            $this->listener = new Doctrine_Db_EventListener_Chain();
zYne's avatar
zYne committed
155 156 157 158 159 160 161 162

        $this->listener->add($listener, $name);
        
        return $this;
    }
    /**
     * getListener
     * 
pookey's avatar
pookey committed
163
     * @return Doctrine_Db_EventListener_Interface|Doctrine_Overloadable
zYne's avatar
zYne committed
164 165 166 167 168 169 170
     */
    public function getListener() {
        return $this->listener;
    }
    /**
     * setListener
     *
pookey's avatar
pookey committed
171 172
     * @param Doctrine_Db_EventListener_Interface|Doctrine_Overloadable $listener
     * @return Doctrine_Db
zYne's avatar
zYne committed
173 174
     */
    public function setListener($listener) {
pookey's avatar
pookey committed
175
        if( ! ($listener instanceof Doctrine_Db_EventListener_Interface) &&
zYne's avatar
zYne committed
176
            ! ($listener instanceof Doctrine_Overloadable))
pookey's avatar
pookey committed
177
            throw new Doctrine_Db_Exception("Couldn't set eventlistener for database handler. EventListeners should implement either Doctrine_Db_EventListener_Interface or Doctrine_Overloadable");
zYne's avatar
zYne committed
178 179 180 181 182

        $this->listener = $listener;

        return $this;
    }
zYne's avatar
zYne committed
183

zYne's avatar
zYne committed
184
    /**
zYne's avatar
zYne committed
185 186 187 188 189 190
     * connect
     * connects into database
     *
     * @return boolean
     */
    public function connect() {
zYne's avatar
zYne committed
191
        if($this->isConnected)
zYne's avatar
zYne committed
192 193
            return false;

zYne's avatar
zYne committed
194
        $this->dbh = new PDO($this->options['dsn'], $this->options['username'], $this->options['password']);
zYne's avatar
zYne committed
195
        $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
pookey's avatar
pookey committed
196
        $this->dbh->setAttribute(PDO::ATTR_STATEMENT_CLASS, array("Doctrine_Db_Statement", array($this)));
197
        $this->isConnected = true;
zYne's avatar
zYne committed
198 199 200 201 202 203
        return true;
    }

    /**
     * getConnection
     *
zYne's avatar
zYne committed
204 205
     * @param string $dsn               PEAR::DB like DSN or PDO like DSN
     * format for PEAR::DB like DSN:    schema://user:password@address/dbname
zYne's avatar
zYne committed
206 207 208 209
     *
     * @return
     */
    public static function getConnection($dsn = null, $username = null, $password = null) {
zYne's avatar
zYne committed
210
        return new self($dsn, $username, $password);
zYne's avatar
zYne committed
211
    }
zYne's avatar
zYne committed
212
    /**
zYne's avatar
zYne committed
213 214 215 216 217 218 219 220 221 222 223 224
     * 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;
zYne's avatar
zYne committed
225 226 227 228 229 230 231 232
    }
    /**
     * parseDSN
     *
     * @param 	string	$dsn
     * @return 	array 	Parsed contents of DSN
     */
    function parseDSN($dsn) {
zYne's avatar
zYne committed
233
        // silence any warnings
zYne's avatar
zYne committed
234 235 236 237 238 239 240 241 242 243
		$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']))
pookey's avatar
pookey committed
244
		  throw new Doctrine_Db_Exception('Empty data source name');
zYne's avatar
zYne committed
245 246

        $drivers = self::getAvailableDrivers();
zYne's avatar
zYne committed
247 248

        $parts['scheme'] = self::driverName($parts['scheme']);
zYne's avatar
zYne committed
249 250

        if( ! in_array($parts['scheme'], $drivers))
pookey's avatar
pookey committed
251
            throw new Doctrine_Db_Exception('Driver '.$parts['scheme'].' not availible or extension not loaded');
zYne's avatar
zYne committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268

        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'] == '/')
pookey's avatar
pookey committed
269
                    throw new Doctrine_Db_Exception('No database availible in data source name');
zYne's avatar
zYne committed
270 271 272 273 274

         		if(isset($parts['path']))
                    $parts['database'] = substr($parts['path'], 1);
                
                if( ! isset($parts['host'])) 
pookey's avatar
pookey committed
275
                    throw new Doctrine_Db_Exception('No hostname set in data source name');
zYne's avatar
zYne committed
276 277 278 279

                $parts['dsn'] = $parts["scheme"].":host=".$parts["host"].";dbname=".$parts["database"];
            break;
            default: 
pookey's avatar
pookey committed
280
                throw new Doctrine_Db_Exception('Unknown driver '.$parts['scheme']);
zYne's avatar
zYne committed
281 282 283 284
        } 

		return $parts;
	}
zYne's avatar
zYne committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
    /**
     * 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
     */
zYne's avatar
zYne committed
301
    public function errorCode() {
zYne's avatar
zYne committed
302 303 304 305 306 307 308 309 310 311 312 313
        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();
    }
    /**
zYne's avatar
zYne committed
314
     * prepare
zYne's avatar
zYne committed
315 316 317
     *
     * @param string $statement
     */
zYne's avatar
zYne committed
318
    public function prepare($statement) {
319 320
        $this->connect();

zYne's avatar
zYne committed
321
        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::PREPARE, $statement);
zYne's avatar
zYne committed
322

zYne's avatar
zYne committed
323
        $this->listener->onPrePrepare($event);
zYne's avatar
zYne committed
324 325

        $stmt = $this->dbh->prepare($statement);
zYne's avatar
zYne committed
326

zYne's avatar
zYne committed
327
        $this->listener->onPrepare($event);
328 329 330

        $this->querySequence++;

zYne's avatar
zYne committed
331
        return $stmt;
zYne's avatar
zYne committed
332 333 334 335 336
    }
    /**
     * query
     *
     * @param string $statement
337
     * @param array $params
pookey's avatar
pookey committed
338
     * @return Doctrine_Db_Statement|boolean
zYne's avatar
zYne committed
339
     */
340 341 342
    public function query($statement, array $params = array()) {
        $this->connect();
        
zYne's avatar
zYne committed
343 344 345 346 347
        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::QUERY, $statement);

        $this->listener->onPreQuery($event);

        if( ! empty($params))
348 349 350
            $stmt = $this->dbh->query($statement)->execute($params);
        else
            $stmt = $this->dbh->query($statement);
zYne's avatar
zYne committed
351

zYne's avatar
zYne committed
352
        $this->listener->onQuery($event);
353 354

        $this->querySequence++;
zYne's avatar
zYne committed
355 356 357 358 359 360 361 362 363 364 365 366

        return $stmt;
    }
    /**
     * quote
     * quotes a string for use in a query
     *
     * @param string $input
     * @return string
     */
    public function quote($input) {
        $this->connect();
zYne's avatar
zYne committed
367

zYne's avatar
zYne committed
368 369 370 371 372 373 374 375 376 377
        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) {
378 379
        $this->connect();

zYne's avatar
zYne committed
380
        $args = func_get_args();
zYne's avatar
zYne committed
381 382
        
        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::EXEC, $statement);
zYne's avatar
zYne committed
383

zYne's avatar
zYne committed
384
        $this->listener->onPreExec($event);
zYne's avatar
zYne committed
385 386 387

        $rows = $this->dbh->exec($statement);

zYne's avatar
zYne committed
388
        $this->listener->onExec($event);
zYne's avatar
zYne committed
389 390

        return $rows;
zYne's avatar
zYne committed
391
    }
zYne's avatar
zYne committed
392 393
    /**
     * fetchAll
394 395
     *
     * @return array
zYne's avatar
zYne committed
396
     */
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
    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 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);
zYne's avatar
zYne committed
420
    }
zYne's avatar
zYne committed
421 422 423
    /**
     * lastInsertId
     *
424
     * @return integer
zYne's avatar
zYne committed
425 426 427 428 429 430 431 432 433 434 435 436
     */
    public function lastInsertId() {
        $this->connect();

        return $this->dbh->lastInsertId();
    }
    /**
     * begins a transaction
     *
     * @return boolean
     */
    public function beginTransaction() {
zYne's avatar
zYne committed
437 438 439
        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::BEGIN);

        $this->listener->onPreBeginTransaction($event);
zYne's avatar
zYne committed
440

zYne's avatar
zYne committed
441 442
        $return = $this->dbh->beginTransaction();

zYne's avatar
zYne committed
443
        $this->listener->onBeginTransaction($event);
zYne's avatar
zYne committed
444 445
    
        return $return;
zYne's avatar
zYne committed
446 447 448 449 450 451 452
    }
    /**
     * commits a transaction
     *
     * @return boolean
     */
    public function commit() {
zYne's avatar
zYne committed
453 454 455
        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::COMMIT);

        $this->listener->onPreCommit($event);
zYne's avatar
zYne committed
456 457 458

        $return = $this->dbh->commit();

zYne's avatar
zYne committed
459
        $this->listener->onCommit($event);
zYne's avatar
zYne committed
460 461

        return $return;
zYne's avatar
zYne committed
462 463 464 465 466 467 468 469
    }
    /**
     * rollBack
     *
     * @return boolean
     */
    public function rollBack() {
        $this->connect();
zYne's avatar
zYne committed
470 471 472 473 474

        $event = new Doctrine_Db_Event($this, Doctrine_Db_Event::ROLLBACK);

        $this->listener->onPreRollback($event);

zYne's avatar
zYne committed
475
        $this->dbh->rollBack();
zYne's avatar
zYne committed
476 477
        
        $this->listener->onRollback($event);
zYne's avatar
zYne committed
478 479 480 481 482 483 484 485 486 487 488
    }
    /**
     * getAttribute
     * retrieves a database connection attribute
     *
     * @param integer $attribute
     * @return mixed
     */
    public function getAttribute($attribute) {
        $this->connect();
        
zYne's avatar
zYne committed
489
        return $this->dbh->getAttribute($attribute);
zYne's avatar
zYne committed
490 491 492 493 494
    }
    /**
     * returns an array of available PDO drivers
     */
    public static function getAvailableDrivers() {
495
        return PDO::getAvailableDrivers();
zYne's avatar
zYne committed
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
    }
    /**
     * 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() {
pookey's avatar
pookey committed
516
        if($this->listener instanceof Doctrine_Db_Profiler)
517
            return $this->listener;
zYne's avatar
zYne committed
518 519 520 521 522 523 524 525
    }
    /**
     * count
     * returns the number of executed queries
     *
     * @return integer
     */
    public function count() {
526
        return $this->querySequence;
zYne's avatar
zYne committed
527
    }  
zYne's avatar
zYne committed
528
}