PDODriver.class.php (9013B)
1 <?php 2 3 // 4 // +----------------------------------------------------------------------+ 5 // | PHP version 4.0 | 6 // +----------------------------------------------------------------------+ 7 // | Copyright (c) 1997-2001 The PHP Group | 8 // +----------------------------------------------------------------------+ 9 // | This source file is subject to version 2.02 of the PHP license, | 10 // | that is bundled with this package in the file LICENSE, and is | 11 // | available at through the world-wide-web at | 12 // | http://www.php.net/license/2_02.txt. | 13 // | If you did not receive a copy of the PHP license and are unable to | 14 // | obtain it through the world-wide-web, please send a note to | 15 // | license@php.net so we can mail you a copy immediately. | 16 // +----------------------------------------------------------------------+ 17 // | Authors: Stig Bakken <ssb@fast.no> | 18 // | Jan Dankert <phpdb@jandankert.de> | 19 // +----------------------------------------------------------------------+ 20 // 21 namespace database\driver; 22 23 use database\Sql; 24 use logger\Logger; 25 use \PDO; 26 use \PDOException; 27 use PDOStatement; 28 use util\exception\DatabaseException; 29 30 /** 31 * Implementation of all database operations in PDO. 32 * 33 * PDO is available since PHP 5.1 and OpenRat CMS forces a newer PHP version than this. So there should be no problem to rely on PDO. 34 * 35 * @author Jan Dankert 36 */ 37 class PDODriver 38 { 39 /** 40 * Die PDO-Verbindung. 41 * 42 * @var PDO 43 */ 44 private $connection; 45 46 47 /** 48 * @var PDOStatement 49 */ 50 public $stmt; 51 52 53 /** 54 * Connect to a database 55 * 56 * @param $conf array connection configuration 57 * @throws DatabaseException 58 */ 59 function connect( $conf ) 60 { 61 if ( !defined('PDO::ATTR_DRIVER_NAME') ) { 62 // This should never happen, because PHP is always bundled with PDO. 63 // but maybe... some installation could miss the module. 64 throw new DatabaseException('PDO unavailable'); 65 } 66 67 $dsn = $conf['dsn' ]; // Optional a pre-configured DSN. 68 $user = $conf['user' ]; 69 $pw = $conf['password']; 70 71 if ( ! $dsn ) { 72 // No DSN is configured, so we are building a DSN. 73 $driver = $conf['driver']; 74 75 if (!in_array($driver,PDO::getAvailableDrivers(),TRUE)) 76 throw new DatabaseException('PDO driver '.$driver.' is not available'); 77 78 $dsnParts = []; 79 if ( $conf['host'] ) 80 $dsnParts[ $driver.':host' ] = $conf['host']; // Hostname for RDBMS with IP-stack 81 elseif ( $conf['file'] ) 82 $dsnParts[ $driver.':'.$conf['file'] ] = $conf['host']; // Filename for SQLITE 83 84 if ( $conf['database'] ) 85 $dsnParts['dbname' ] = $conf['database']; 86 if ( $conf['port'] ) 87 $dsnParts['port' ] = $conf['port']; 88 if ( $conf['charset'] ) 89 $dsnParts['charset'] = $conf['charset' ]; 90 91 // Building the DSN for PDO. 92 $dsn = implode('; ',array_map( function($key,$value) { 93 return $key.'='.$value; 94 },array_keys($dsnParts),$dsnParts)); 95 } 96 97 // we must have a prefix or suffix 98 // this is because some table names are reserved words in some RDBMS 99 if ( ! $conf['prefix'] && ! $conf['suffix'] ) 100 throw new DatabaseException('database tables must have a prefix or a suffix, both are empty.'); 101 102 $options = array(); 103 foreach( $conf as $c ) 104 if ( is_string($c) && substr($c,0,7) == 'option_' ) 105 $options[substr($c,8)] = $conf[$c]; 106 107 if ( $conf['persistent']) 108 // From the docs: 109 // "Many web applications will benefit from making persistent connections to database servers. 110 // Persistent connections are not closed at the end of the script, but are cached and re-used 111 // when another script requests a connection using the same credentials." 112 // "The persistent connection cache allows you to avoid the overhead of establishing a new 113 // connection every time a script needs to talk to a database, resulting in a faster web application." 114 $options[ PDO::ATTR_PERSISTENT ] = true; 115 116 // From the docs: 117 // "try to use native prepared statements (if FALSE). 118 // It will always fall back to emulating the prepared statement if the driver cannot successfully prepare the current query" 119 $options[ PDO::ATTR_EMULATE_PREPARES ] = false; 120 121 // Convert numeric values to strings when fetching => NO 122 $options[ PDO::ATTR_STRINGIFY_FETCHES ] = false; 123 124 // From the docs: 125 // "If this value is FALSE, PDO attempts to disable autocommit so that the connection begins a transaction." 126 // We do NOT need transactions for reading actions (GET requests). 127 // We are opening a transaction with PDO::beginTransaction at the beginning of a POST-request. 128 // do NOT set this to false, otherwise there will be left open transactions. 129 //$options[ PDO::ATTR_AUTOCOMMIT ] = true; 130 131 // We like Exceptions 132 $options[ PDO::ERRMODE_EXCEPTION ] = true; 133 $options[ PDO::ATTR_DEFAULT_FETCH_MODE ] = PDO::FETCH_ASSOC; 134 135 try 136 { 137 $this->connection = new PDO($dsn, $user, $pw, $options); 138 } 139 catch(\PDOException $e) 140 { 141 throw new DatabaseException("Could not connect to database with DSN '$dsn'",$e); 142 } 143 144 // This should never happen, because PDO should throw an exception if the connection fails. 145 if ( !is_object($this->connection) ) 146 throw new DatabaseException("Could not connect to database with DSN '$dsn', Reason: ".PDO::errorInfo() ); 147 } 148 149 150 /** 151 * Disconnects the database connection. 152 * 153 * @return bool 154 */ 155 public function disconnect() 156 { 157 // There is no disconnection-function. 158 // So the GC will call the finalize-method of the connection object. 159 $this->connection = null; 160 161 return true; 162 } 163 164 165 /** 166 * @param $stmt PDOStatement 167 * @param $query Sql 168 * @return PDOStatement 169 */ 170 public function execute($stmt, $query) 171 { 172 $erg = $stmt->execute(); 173 174 if ( $erg === false ) 175 throw new DatabaseException( 'Could not execute prepared statement "'.$query->query.'": '.implode('/',$stmt->errorInfo()) ); 176 177 return $stmt; 178 } 179 180 181 /** 182 * @param $stmt PDOStatement 183 * @return array Row 184 */ 185 public function fetchAssocRow($stmt) 186 { 187 return $stmt->fetch( PDO::FETCH_ASSOC ); 188 } 189 190 191 /** 192 * Fetches all rows from the resultset 193 * @param $stmt PDOStatement 194 * @return array Row 195 */ 196 public function fetchAllRows($stmt) 197 { 198 return $stmt->fetchAll( PDO::FETCH_ASSOC ); 199 } 200 201 202 /** 203 * Fetches the next row with a numbered-based array. 204 * @param $stmt PDOStatement 205 * @return array Row 206 */ 207 public function fetchIndexedRow($stmt) 208 { 209 return $stmt->fetch( PDO::FETCH_NUM ); 210 } 211 212 213 214 /** 215 * Fetches the first column of the next row. 216 * 217 * @param $stmt PDOStatement 218 * @return mixed Row 219 */ 220 public function fetchFirstColumn($stmt) { 221 return $stmt->fetchColumn(); 222 } 223 224 225 /** 226 * Fetches all first columns from the result set 227 * 228 * @param $stmt PDOStatement 229 * @return array 230 */ 231 public function fetchAllFirstColumn($stmt) { 232 return $stmt->fetchAll( PDO::FETCH_COLUMN ); 233 } 234 235 236 /** 237 * Prepares a SQL query and gets the Statement. 238 * 239 * @param $query string SQL-query 240 * @param $param array parameters 241 * @return PDOStatement 242 * @throws DatabaseException 243 */ 244 public function prepare( $query,$param) 245 { 246 $offset = 0; 247 foreach( $param as $name=>$pos) 248 { 249 $name = ':'.$name; 250 $pos += $offset; 251 $query = substr($query,0,$pos).$name.substr($query,$pos); 252 253 $offset = $offset + strlen($name); 254 } 255 256 $stmt = $this->connection->prepare($query); 257 258 if ( $stmt === false ) 259 throw new DatabaseException("Could not prepare statement:\n$query\nCause: ".implode(' / ',$this->connection->errorInfo()) ); 260 261 return $stmt; 262 } 263 264 265 /** 266 * Binding a parameter value. 267 * 268 * @param $stmt PDOStatement 269 * @param $param 270 * @param $value 271 */ 272 public function bind( $stmt,$param,$value ) 273 { 274 $name = ':'.$param; 275 276 if ( is_string($value) ) 277 $type = PDO::PARAM_STR; 278 elseif( is_int($value)) 279 $type = PDO::PARAM_INT; 280 elseif( is_null($value)) 281 $type = PDO::PARAM_NULL; 282 else 283 throw new DatabaseException( 'Unknown type for parameter '.$name.': '.gettype($value) ); 284 285 $stmt->bindValue($name,$value,$type); 286 } 287 288 289 290 /** 291 * Startet eine Transaktion. 292 */ 293 public function start() 294 { 295 $this->connection->beginTransaction(); 296 } 297 298 299 300 /** 301 * Beendet eine Transaktion. 302 */ 303 public function commit() 304 { 305 $this->connection->commit(); 306 } 307 308 309 /** 310 * Bricht eine Transaktion ab. 311 */ 312 public function rollback() 313 { 314 try 315 { 316 $this->connection->rollBack(); 317 } 318 catch ( PDOException $e ) 319 { 320 // Kommt vor, wenn keine Transaktion existiert. 321 } 322 } 323 324 325 /** 326 * Why this? See http://e-mats.org/2008/07/fatal-error-exception-thrown-without-a-stack-frame-in-unknown-on-line-0/ 327 * 328 * @return array 329 */ 330 function __sleep() { 331 return array(); 332 } 333 334 }