&$value ) { if ( is_integer( $key ) ) { if ( is_null( $value ) ) { $statement->bindValue( $key + 1, NULL, \PDO::PARAM_NULL ); } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) { $statement->bindParam( $key + 1, $value, \PDO::PARAM_INT ); } else { $statement->bindParam( $key + 1, $value, \PDO::PARAM_STR ); } } else { if ( is_null( $value ) ) { $statement->bindValue( $key, NULL, \PDO::PARAM_NULL ); } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) { $statement->bindParam( $key, $value, \PDO::PARAM_INT ); } else { $statement->bindParam( $key, $value, \PDO::PARAM_STR ); } } } } /** * This method runs the actual SQL query and binds a list of parameters to the query. * slots. The result of the query will be stored in the protected property * $rs (always array). The number of rows affected (result of rowcount, if supported by database) * is stored in protected property $affectedRows. If the debug flag is set * this function will send debugging output to screen buffer. * * @param string $sql the SQL string to be send to database server * @param array $bindings the values that need to get bound to the query slots * @param array $options * * @return mixed * @throws SQL */ protected function runQuery( $sql, $bindings, $options = array() ) { $this->connect(); if ( $this->loggingEnabled && $this->logger ) { $this->logger->log( $sql, $bindings ); } try { if ( strpos( 'pgsql', $this->dsn ) === 0 ) { if ( defined( '\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' ) ) { $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) ); } else { $statement = $this->pdo->prepare( $sql ); } } else { $statement = $this->pdo->prepare( $sql ); } $this->bindParams( $statement, $bindings ); $statement->execute(); $this->queryCounter ++; $this->affectedRows = $statement->rowCount(); if ( $statement->columnCount() ) { $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL; if ( isset( $options['noFetch'] ) && $options['noFetch'] ) { $this->resultArray = array(); return $statement; } $this->resultArray = $statement->fetchAll( $fetchStyle ); if ( $this->loggingEnabled && $this->logger ) { $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' ); } } else { $this->resultArray = array(); } } catch ( \PDOException $e ) { //Unfortunately the code field is supposed to be int by default (php) //So we need a property to convey the SQL State code. $err = $e->getMessage(); if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err ); $exception = new SQL( $err, 0 ); $exception->setSQLState( $e->getCode() ); throw $exception; } } /** * Try to fix MySQL character encoding problems. * MySQL < 5.5 does not support proper 4 byte unicode but they * seem to have added it with version 5.5 under a different label: utf8mb4. * We try to select the best possible charset based on your version data. * * @return void */ protected function setEncoding() { $driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME ); $version = floatval( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) ); if ($driver === 'mysql') { $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8'; $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect $this->pdo->exec(' SET NAMES '. $encoding); //also for current connection $this->mysqlEncoding = $encoding; } } /** * Constructor. You may either specify dsn, user and password or * just give an existing PDO connection. * * Examples: * $driver = new RPDO($dsn, $user, $password); * $driver = new RPDO($existingConnection); * * @param string|object $dsn database connection string * @param string $user optional, usename to sign in * @param string $pass optional, password for connection login * * @return void */ public function __construct( $dsn, $user = NULL, $pass = NULL ) { if ( is_object( $dsn ) ) { $this->pdo = $dsn; $this->isConnected = TRUE; $this->setEncoding(); $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION ); $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC ); // make sure that the dsn at least contains the type $this->dsn = $this->getDatabaseType(); } else { $this->dsn = $dsn; $this->connectInfo = array( 'pass' => $pass, 'user' => $user ); } //PHP 5.3 PDO SQLite has a bug with large numbers: if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) || defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) { $this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis. } elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) { $this->max = 2147483647; //bindParam in pdo_cubrid also fails... } else { $this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause) } } /** * Returns the best possible encoding for MySQL based on version data. * * @return string */ public function getMysqlEncoding() { return $this->mysqlEncoding; } /** * Whether to bind all parameters as strings. * If set to TRUE this will cause all integers to be bound as STRINGS. * This will NOT affect NULL values. * * @param boolean $yesNo pass TRUE to bind all parameters as strings. * * @return void */ public function setUseStringOnlyBinding( $yesNo ) { $this->flagUseStringOnlyBinding = (boolean) $yesNo; } /** * Sets the maximum value to be bound as integer, normally * this value equals PHP's MAX INT constant, however sometimes * PDO driver bindings cannot bind large integers as integers. * This method allows you to manually set the max integer binding * value to manage portability/compatibility issues among different * PHP builds. This method will return the old value. * * @param integer $max maximum value for integer bindings * * @return integer */ public function setMaxIntBind( $max ) { if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' ); $oldMax = $this->max; $this->max = $max; return $oldMax; } /** * Establishes a connection to the database using PHP\PDO * functionality. If a connection has already been established this * method will simply return directly. This method also turns on * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as * PDO-FETCH-ASSOC. * * @return void */ public function connect() { if ( $this->isConnected ) return; try { $user = $this->connectInfo['user']; $pass = $this->connectInfo['pass']; $this->pdo = new \PDO( $this->dsn, $user, $pass ); $this->setEncoding(); $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, TRUE ); //cant pass these as argument to constructor, CUBRID driver does not understand... $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION ); $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC ); $this->isConnected = TRUE; } catch ( \PDOException $exception ) { $matches = array(); $dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?'; throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() ); } } /** * Directly sets PDO instance into driver. * This method might improve performance, however since the driver does * not configure this instance terrible things may happen... only use * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and * you know your database server VERY WELL. * * @param PDO $pdo PDO instance * * @return void */ public function setPDO( \PDO $pdo ) { $this->pdo = $pdo; } /** * @see Driver::GetAll */ public function GetAll( $sql, $bindings = array() ) { $this->runQuery( $sql, $bindings ); return $this->resultArray; } /** * @see Driver::GetAssocRow */ public function GetAssocRow( $sql, $bindings = array() ) { $this->runQuery( $sql, $bindings, array( 'fetchStyle' => \PDO::FETCH_ASSOC ) ); return $this->resultArray; } /** * @see Driver::GetCol */ public function GetCol( $sql, $bindings = array() ) { $rows = $this->GetAll( $sql, $bindings ); $cols = array(); if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) { foreach ( $rows as $row ) { $cols[] = array_shift( $row ); } } return $cols; } /** * @see Driver::GetOne */ public function GetOne( $sql, $bindings = array() ) { $arr = $this->GetAll( $sql, $bindings ); $res = NULL; if ( !is_array( $arr ) ) return NULL; if ( count( $arr ) === 0 ) return NULL; $row1 = array_shift( $arr ); if ( !is_array( $row1 ) ) return NULL; if ( count( $row1 ) === 0 ) return NULL; $col1 = array_shift( $row1 ); return $col1; } /** * Alias for getOne(). * Backward compatibility. * * @param string $sql SQL * @param array $bindings bindings * * @return mixed */ public function GetCell( $sql, $bindings = array() ) { return $this->GetOne( $sql, $bindings ); } /** * @see Driver::GetRow */ public function GetRow( $sql, $bindings = array() ) { $arr = $this->GetAll( $sql, $bindings ); return array_shift( $arr ); } /** * @see Driver::Excecute */ public function Execute( $sql, $bindings = array() ) { $this->runQuery( $sql, $bindings ); return $this->affectedRows; } /** * @see Driver::GetInsertID */ public function GetInsertID() { $this->connect(); return (int) $this->pdo->lastInsertId(); } /** * @see Driver::GetCursor */ public function GetCursor( $sql, $bindings = array() ) { $statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) ); $cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC ); return $cursor; } /** * @see Driver::Affected_Rows */ public function Affected_Rows() { $this->connect(); return (int) $this->affectedRows; } /** * Toggles debug mode. In debug mode the driver will print all * SQL to the screen together with some information about the * results. * * @param boolean $trueFalse turn on/off * @param Logger $logger logger instance * * @return void */ public function setDebugMode( $tf, $logger = NULL ) { $this->connect(); $this->loggingEnabled = (bool) $tf; if ( $this->loggingEnabled and !$logger ) { $logger = new RDefault(); } $this->setLogger( $logger ); } /** * Injects Logger object. * Sets the logger instance you wish to use. * * @param Logger $logger the logger instance to be used for logging * * @return void */ public function setLogger( Logger $logger ) { $this->logger = $logger; } /** * Gets Logger object. * Returns the currently active Logger instance. * * @return Logger */ public function getLogger() { return $this->logger; } /** * @see Driver::StartTrans */ public function StartTrans() { $this->connect(); $this->pdo->beginTransaction(); } /** * @see Driver::CommitTrans */ public function CommitTrans() { $this->connect(); $this->pdo->commit(); } /** * @see Driver::FailTrans */ public function FailTrans() { $this->connect(); $this->pdo->rollback(); } /** * Returns the name of database driver for PDO. * Uses the PDO attribute DRIVER NAME to obtain the name of the * PDO driver. * * @return string */ public function getDatabaseType() { $this->connect(); return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME ); } /** * Returns the version number of the database. * * @return mixed */ public function getDatabaseVersion() { $this->connect(); return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION ); } /** * Returns the underlying PHP PDO instance. * * @return PDO */ public function getPDO() { $this->connect(); return $this->pdo; } /** * Closes database connection by destructing PDO. * * @return void */ public function close() { $this->pdo = NULL; $this->isConnected = FALSE; } /** * Returns TRUE if the current PDO instance is connected. * * @return boolean */ public function isConnected() { return $this->isConnected && $this->pdo; } /** * Toggles logging, enables or disables logging. * * @param boolean $enable TRUE to enable logging * * @return self */ public function setEnableLogging( $enable ) { $this->loggingEnabled = (boolean) $enable; } /** * Resets the internal Query Counter. * * @return self */ public function resetCounter() { $this->queryCounter = 0; return $this; } /** * Returns the number of SQL queries processed. * * @return integer */ public function getQueryCount() { return $this->queryCounter; } /** * Returns the maximum value treated as integer parameter * binding. * * This method is mainly for testing purposes but it can help * you solve some issues relating to integer bindings. * * @return integer */ public function getIntegerBindingMax() { return $this->max; } }