3 namespace RedBeanPHP\Driver;
5 use RedBeanPHP\Driver as Driver;
6 use RedBeanPHP\Logger as Logger;
7 use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
8 use RedBeanPHP\RedException as RedException;
9 use RedBeanPHP\RedException\SQL as SQL;
10 use RedBeanPHP\Logger\RDefault as RDefault;
11 use RedBeanPHP\PDOCompatible as PDOCompatible;
12 use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
16 * This Driver implements the RedBean Driver API.
17 * for RedBeanPHP. This is the standard / default database driver
20 * @file RedBeanPHP/PDO.php
21 * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes
25 * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
26 * This source file is subject to the BSD/GPLv2 License that is bundled
27 * with this source code in the file license.txt.
29 class RPDO implements Driver
44 protected $loggingEnabled = FALSE;
49 protected $logger = NULL;
59 protected $affectedRows;
64 protected $resultArray;
69 protected $connectInfo = array();
74 protected $isConnected = FALSE;
79 protected $flagUseStringOnlyBinding = FALSE;
84 protected $queryCounter = 0;
89 protected $mysqlEncoding = '';
92 * Binds parameters. This method binds parameters to a PDOStatement for
93 * Query Execution. This method binds parameters as NULL, INTEGER or STRING
94 * and supports both named keys and question mark keys.
96 * @param PDOStatement $statement PDO Statement instance
97 * @param array $bindings values that need to get bound to the statement
101 protected function bindParams( $statement, $bindings )
103 foreach ( $bindings as $key => &$value ) {
104 if ( is_integer( $key ) ) {
105 if ( is_null( $value ) ) {
106 $statement->bindValue( $key + 1, NULL, \PDO::PARAM_NULL );
107 } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
108 $statement->bindParam( $key + 1, $value, \PDO::PARAM_INT );
110 $statement->bindParam( $key + 1, $value, \PDO::PARAM_STR );
113 if ( is_null( $value ) ) {
114 $statement->bindValue( $key, NULL, \PDO::PARAM_NULL );
115 } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
116 $statement->bindParam( $key, $value, \PDO::PARAM_INT );
118 $statement->bindParam( $key, $value, \PDO::PARAM_STR );
125 * This method runs the actual SQL query and binds a list of parameters to the query.
126 * slots. The result of the query will be stored in the protected property
127 * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
128 * is stored in protected property $affectedRows. If the debug flag is set
129 * this function will send debugging output to screen buffer.
131 * @param string $sql the SQL string to be send to database server
132 * @param array $bindings the values that need to get bound to the query slots
133 * @param array $options
138 protected function runQuery( $sql, $bindings, $options = array() )
141 if ( $this->loggingEnabled && $this->logger ) {
142 $this->logger->log( $sql, $bindings );
145 if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
146 if ( defined( '\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' ) ) {
147 $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) );
149 $statement = $this->pdo->prepare( $sql );
152 $statement = $this->pdo->prepare( $sql );
154 $this->bindParams( $statement, $bindings );
155 $statement->execute();
156 $this->queryCounter ++;
157 $this->affectedRows = $statement->rowCount();
158 if ( $statement->columnCount() ) {
159 $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
160 if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
161 $this->resultArray = array();
164 $this->resultArray = $statement->fetchAll( $fetchStyle );
165 if ( $this->loggingEnabled && $this->logger ) {
166 $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
169 $this->resultArray = array();
171 } catch ( \PDOException $e ) {
172 //Unfortunately the code field is supposed to be int by default (php)
173 //So we need a property to convey the SQL State code.
174 $err = $e->getMessage();
175 if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
176 $exception = new SQL( $err, 0 );
177 $exception->setSQLState( $e->getCode() );
183 * Try to fix MySQL character encoding problems.
184 * MySQL < 5.5 does not support proper 4 byte unicode but they
185 * seem to have added it with version 5.5 under a different label: utf8mb4.
186 * We try to select the best possible charset based on your version data.
190 protected function setEncoding()
192 $driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
193 $version = floatval( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
194 if ($driver === 'mysql') {
195 $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8';
196 $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect
197 $this->pdo->exec(' SET NAMES '. $encoding); //also for current connection
198 $this->mysqlEncoding = $encoding;
203 * Constructor. You may either specify dsn, user and password or
204 * just give an existing PDO connection.
207 * $driver = new RPDO($dsn, $user, $password);
208 * $driver = new RPDO($existingConnection);
210 * @param string|object $dsn database connection string
211 * @param string $user optional, usename to sign in
212 * @param string $pass optional, password for connection login
216 public function __construct( $dsn, $user = NULL, $pass = NULL )
218 if ( is_object( $dsn ) ) {
220 $this->isConnected = TRUE;
221 $this->setEncoding();
222 $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
223 $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
224 // make sure that the dsn at least contains the type
225 $this->dsn = $this->getDatabaseType();
228 $this->connectInfo = array( 'pass' => $pass, 'user' => $user );
231 //PHP 5.3 PDO SQLite has a bug with large numbers:
232 if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) || defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
233 $this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
234 } elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
235 $this->max = 2147483647; //bindParam in pdo_cubrid also fails...
237 $this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
242 * Returns the best possible encoding for MySQL based on version data.
246 public function getMysqlEncoding()
248 return $this->mysqlEncoding;
252 * Whether to bind all parameters as strings.
253 * If set to TRUE this will cause all integers to be bound as STRINGS.
254 * This will NOT affect NULL values.
256 * @param boolean $yesNo pass TRUE to bind all parameters as strings.
260 public function setUseStringOnlyBinding( $yesNo )
262 $this->flagUseStringOnlyBinding = (boolean) $yesNo;
266 * Sets the maximum value to be bound as integer, normally
267 * this value equals PHP's MAX INT constant, however sometimes
268 * PDO driver bindings cannot bind large integers as integers.
269 * This method allows you to manually set the max integer binding
270 * value to manage portability/compatibility issues among different
271 * PHP builds. This method will return the old value.
273 * @param integer $max maximum value for integer bindings
277 public function setMaxIntBind( $max )
279 if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
280 $oldMax = $this->max;
286 * Establishes a connection to the database using PHP\PDO
287 * functionality. If a connection has already been established this
288 * method will simply return directly. This method also turns on
289 * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
294 public function connect()
296 if ( $this->isConnected ) return;
298 $user = $this->connectInfo['user'];
299 $pass = $this->connectInfo['pass'];
300 $this->pdo = new \PDO(
305 $this->setEncoding();
306 $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, TRUE );
307 //cant pass these as argument to constructor, CUBRID driver does not understand...
308 $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
309 $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
310 $this->isConnected = TRUE;
311 } catch ( \PDOException $exception ) {
313 $dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
314 throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
319 * Directly sets PDO instance into driver.
320 * This method might improve performance, however since the driver does
321 * not configure this instance terrible things may happen... only use
322 * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
323 * you know your database server VERY WELL.
325 * @param PDO $pdo PDO instance
329 public function setPDO( \PDO $pdo ) {
334 * @see Driver::GetAll
336 public function GetAll( $sql, $bindings = array() )
338 $this->runQuery( $sql, $bindings );
339 return $this->resultArray;
343 * @see Driver::GetAssocRow
345 public function GetAssocRow( $sql, $bindings = array() )
347 $this->runQuery( $sql, $bindings, array(
348 'fetchStyle' => \PDO::FETCH_ASSOC
351 return $this->resultArray;
355 * @see Driver::GetCol
357 public function GetCol( $sql, $bindings = array() )
359 $rows = $this->GetAll( $sql, $bindings );
361 if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) {
362 foreach ( $rows as $row ) {
363 $cols[] = array_shift( $row );
371 * @see Driver::GetOne
373 public function GetOne( $sql, $bindings = array() )
375 $arr = $this->GetAll( $sql, $bindings );
377 if ( !is_array( $arr ) ) return NULL;
378 if ( count( $arr ) === 0 ) return NULL;
379 $row1 = array_shift( $arr );
380 if ( !is_array( $row1 ) ) return NULL;
381 if ( count( $row1 ) === 0 ) return NULL;
382 $col1 = array_shift( $row1 );
387 * Alias for getOne().
388 * Backward compatibility.
390 * @param string $sql SQL
391 * @param array $bindings bindings
395 public function GetCell( $sql, $bindings = array() )
397 return $this->GetOne( $sql, $bindings );
401 * @see Driver::GetRow
403 public function GetRow( $sql, $bindings = array() )
405 $arr = $this->GetAll( $sql, $bindings );
406 return array_shift( $arr );
410 * @see Driver::Excecute
412 public function Execute( $sql, $bindings = array() )
414 $this->runQuery( $sql, $bindings );
415 return $this->affectedRows;
419 * @see Driver::GetInsertID
421 public function GetInsertID()
425 return (int) $this->pdo->lastInsertId();
429 * @see Driver::GetCursor
431 public function GetCursor( $sql, $bindings = array() )
433 $statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
434 $cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
439 * @see Driver::Affected_Rows
441 public function Affected_Rows()
444 return (int) $this->affectedRows;
448 * Toggles debug mode. In debug mode the driver will print all
449 * SQL to the screen together with some information about the
452 * @param boolean $trueFalse turn on/off
453 * @param Logger $logger logger instance
457 public function setDebugMode( $tf, $logger = NULL )
460 $this->loggingEnabled = (bool) $tf;
461 if ( $this->loggingEnabled and !$logger ) {
462 $logger = new RDefault();
464 $this->setLogger( $logger );
468 * Injects Logger object.
469 * Sets the logger instance you wish to use.
471 * @param Logger $logger the logger instance to be used for logging
475 public function setLogger( Logger $logger )
477 $this->logger = $logger;
481 * Gets Logger object.
482 * Returns the currently active Logger instance.
486 public function getLogger()
488 return $this->logger;
492 * @see Driver::StartTrans
494 public function StartTrans()
497 $this->pdo->beginTransaction();
501 * @see Driver::CommitTrans
503 public function CommitTrans()
506 $this->pdo->commit();
510 * @see Driver::FailTrans
512 public function FailTrans()
515 $this->pdo->rollback();
519 * Returns the name of database driver for PDO.
520 * Uses the PDO attribute DRIVER NAME to obtain the name of the
525 public function getDatabaseType()
529 return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
533 * Returns the version number of the database.
537 public function getDatabaseVersion()
540 return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
544 * Returns the underlying PHP PDO instance.
548 public function getPDO()
555 * Closes database connection by destructing PDO.
559 public function close()
562 $this->isConnected = FALSE;
566 * Returns TRUE if the current PDO instance is connected.
570 public function isConnected()
572 return $this->isConnected && $this->pdo;
576 * Toggles logging, enables or disables logging.
578 * @param boolean $enable TRUE to enable logging
582 public function setEnableLogging( $enable )
584 $this->loggingEnabled = (boolean) $enable;
588 * Resets the internal Query Counter.
592 public function resetCounter()
594 $this->queryCounter = 0;
599 * Returns the number of SQL queries processed.
603 public function getQueryCount()
605 return $this->queryCounter;
609 * Returns the maximum value treated as integer parameter
612 * This method is mainly for testing purposes but it can help
613 * you solve some issues relating to integer bindings.
617 public function getIntegerBindingMax()