PHP Classes

File: vendor/gabordemooij/redbean/RedBeanPHP/Driver/RPDO.php

Recommend this page to a friend!
  Classes of Adrian M   upMVC   vendor/gabordemooij/redbean/RedBeanPHP/Driver/RPDO.php   Download  
File: vendor/gabordemooij/redbean/RedBeanPHP/Driver/RPDO.php
Role: Class source
Content type: text/plain
Description: Class source
Class: upMVC
Pure PHP web development without other frameworks
Author: By
Last change:
Date: 1 month ago
Size: 26,106 bytes
 

Contents

Class file image Download
<?php namespace RedBeanPHP\Driver; use RedBeanPHP\Driver as Driver; use RedBeanPHP\Logger as Logger; use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; use RedBeanPHP\RedException as RedException; use RedBeanPHP\RedException\SQL as SQL; use RedBeanPHP\Logger\RDefault as RDefault; use RedBeanPHP\PDOCompatible as PDOCompatible; use RedBeanPHP\Cursor\PDOCursor as PDOCursor; /** * PDO Driver * This Driver implements the RedBean Driver API. * for RedBeanPHP. This is the standard / default database driver * for RedBeanPHP. * * @file RedBeanPHP/PDO.php * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes * @license BSD/GPLv2 * * @copyright * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community * This source file is subject to the BSD/GPLv2 License that is bundled * with this source code in the file license.txt. */ class RPDO implements Driver { /** * @var integer */ protected $max; /** * @var string */ protected $dsn; /** * @var boolean */ protected $loggingEnabled = FALSE; /** * @var Logger|NULL */ protected $logger = NULL; /** * @var \PDO|NULL */ protected $pdo; /** * @var integer */ protected $affectedRows; /** * @var array */ protected $resultArray; /** * @var array */ protected $connectInfo = array(); /** * @var boolean */ protected $isConnected = FALSE; /** * @var bool */ protected $flagUseStringOnlyBinding = FALSE; /** * @var integer */ protected $queryCounter = 0; /** * @var string */ protected $mysqlCharset = ''; /** * @var string */ protected $mysqlCollate = ''; /** * @var boolean */ protected $stringifyFetches = TRUE; /** * @var string|NULL */ protected $initSQL = NULL; /** * @var callable|NULL */ protected $initCode = NULL; /** * Binds parameters. This method binds parameters to a PDOStatement for * Query Execution. This method binds parameters as NULL, INTEGER or STRING * and supports both named keys and question mark keys. * * @param \PDOStatement $statement PDO Statement instance * @param array $bindings values that need to get bound to the statement * * @return void */ protected function bindParams( $statement, $bindings ) { foreach ( $bindings as $key => &$value ) { $k = is_integer( $key ) ? $key + 1 : $key; if ( is_array( $value ) && count( $value ) == 2 ) { $paramType = end( $value ); $value = reset( $value ); } else { $paramType = NULL; } if ( is_null( $value ) ) { $statement->bindValue( $k, NULL, \PDO::PARAM_NULL ); continue; } if ( $paramType != \PDO::PARAM_INT && $paramType != \PDO::PARAM_STR ) { if ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) { $paramType = \PDO::PARAM_INT; } else { $paramType = \PDO::PARAM_STR; } } $statement->bindParam( $k, $value, $paramType ); } } /** * 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 */ public 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 ( isset( $options['noFetch'] ) && $options['noFetch'] ) { $this->resultArray = array(); return $statement; } if ( $statement->columnCount() ) { $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL; if ( is_null( $fetchStyle) ) { $this->resultArray = $statement->fetchAll(); } else { $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, $e ); $exception->setSQLState( $e->getCode() ); $exception->setDriverDetails( $e->errorInfo ); throw $exception; } } /** * Try to fix MySQL character encoding problems. * MySQL < 5.5.3 does not support proper 4 byte unicode but they * seem to have added it with version 5.5.3 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 ); if ($driver === 'mysql') { $charset = $this->hasCap( 'utf8mb4' ) ? 'utf8mb4' : 'utf8'; $collate = $this->hasCap( 'utf8mb4_520' ) ? '_unicode_520_ci' : '_unicode_ci'; $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '. $charset ); //on every re-connect /* #624 removed space before SET NAMES because it causes trouble with ProxySQL */ $this->pdo->exec('SET NAMES '. $charset); //also for current connection $this->mysqlCharset = $charset; $this->mysqlCollate = $charset . $collate; } } /** * Determine if a database supports a particular feature. * Currently this function can be used to detect the following features: * * - utf8mb4 * - utf8mb4 520 * * Usage: * * <code> * $this->hasCap( 'utf8mb4_520' ); * </code> * * By default, RedBeanPHP uses this method under the hood to make sure * you use the latest UTF8 encoding possible for your database. * * @param string $db_cap identifier of database capability * * @return int|false Whether the database feature is supported, FALSE otherwise. **/ protected function hasCap( $db_cap ) { $compare = FALSE; $version = $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ); switch ( strtolower( $db_cap ) ) { case 'utf8mb4': //oneliner, to boost code coverage (coverage does not span versions) if ( version_compare( $version, '5.5.3', '<' ) ) { return FALSE; } $client_version = $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION ); /* * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server. * mysqlnd has supported utf8mb4 since 5.0.9. */ if ( strpos( $client_version, 'mysqlnd' ) !== FALSE ) { $client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version ); $compare = version_compare( $client_version, '5.0.9', '>=' ); } else { $compare = version_compare( $client_version, '5.5.3', '>=' ); } break; case 'utf8mb4_520': $compare = version_compare( $version, '5.6', '>=' ); break; } return $compare; } /** * Constructor. You may either specify dsn, user and password or * just give an existing PDO connection. * * Usage: * * <code> * $driver = new RPDO( $dsn, $user, $password ); * </code> * * The example above illustrates how to create a driver * instance from a database connection string (dsn), a username * and a password. It's also possible to pass a PDO object. * * Usage: * * <code> * $driver = new RPDO( $existingConnection ); * </code> * * The second example shows how to create an RPDO instance * from an existing PDO object. * * @param string|\PDO $dsn database connection string * @param string $user optional, username to sign in * @param string $pass optional, password for connection login * * @return void */ public function __construct( $dsn, $user = NULL, $pass = NULL, $options = array() ) { 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 ); if (is_array($options)) $this->connectInfo['options'] = $options; } //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) } } /** * Sets PDO in stringify fetch mode. * If set to TRUE, this method will make sure all data retrieved from * the database will be fetched as a string. Default: TRUE. * * To set it to FALSE... * * Usage: * * <code> * R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE ); * </code> * * Important! * Note, this method only works if you set the value BEFORE the connection * has been establish. Also, this setting ONLY works with SOME drivers. * It's up to the driver to honour this setting. * * @param boolean $bool */ public function stringifyFetches( $bool ) { $this->stringifyFetches = $bool; } /** * Returns the best possible encoding for MySQL based on version data. * This method can be used to obtain the best character set parameters * possible for your database when constructing a table creation query * containing clauses like: CHARSET=... COLLATE=... * This is a MySQL-specific method and not part of the driver interface. * * Usage: * * <code> * $charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE ); * </code> * * @param boolean $retCol pass TRUE to return both charset/collate * * @return string|array */ public function getMysqlEncoding( $retCol = FALSE ) { if( $retCol ) return array( 'charset' => $this->mysqlCharset, 'collate' => $this->mysqlCollate ); return $this->mysqlCharset; } /** * 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; if ( $this->loggingEnabled && $this->logger && method_exists($this->logger,'setUseStringOnlyBinding')) { $this->logger->setUseStringOnlyBinding( $this->flagUseStringOnlyBinding ); } } /** * 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; } /** * Sets initialization code to execute upon connecting. * * @param callable|NULL $code * * @return void */ public function setInitCode($code) { $this->initCode= $code; } /** * 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']; $options = array(); if (isset($this->connectInfo['options']) && is_array($this->connectInfo['options'])) { $options = $this->connectInfo['options']; } $this->pdo = new \PDO( $this->dsn, $user, $pass, $options ); $this->setEncoding(); $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $this->stringifyFetches ); //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; /* run initialisation query if any */ if ( $this->initSQL !== NULL ) { $this->Execute( $this->initSQL ); $this->initSQL = NULL; } if ( $this->initCode !== NULL ) { $code = $this->initCode; $code( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) ); } } 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. * * - connected TRUE|FALSE (treat this instance as connected, default: TRUE) * - setEncoding TRUE|FALSE (let RedBeanPHP set encoding for you, default: TRUE) * - setAttributes TRUE|FALSE (let RedBeanPHP set attributes for you, default: TRUE)* * - setDSNString TRUE|FALSE (extract DSN string from PDO instance, default: TRUE) * - stringFetch TRUE|FALSE (whether you want to stringify fetches or not, default: TRUE) * - runInitCode TRUE|FALSE (run init code if any, default: TRUE) * * *attributes: * - RedBeanPHP will ask database driver to throw Exceptions on errors (recommended for compatibility) * - RedBeanPHP will ask database driver to use associative arrays when fetching (recommended for compatibility) * * @param \PDO $pdo PDO instance * @param array $options Options to apply * * @return void */ public function setPDO( \PDO $pdo, $options = array() ) { $this->pdo = $pdo; $connected = TRUE; $setEncoding = TRUE; $setAttributes = TRUE; $setDSNString = TRUE; $runInitCode = TRUE; $stringFetch = TRUE; if ( isset($options['connected']) ) $connected = $options['connected']; if ( isset($options['setEncoding']) ) $setEncoding = $options['setEncoding']; if ( isset($options['setAttributes']) ) $setAttributes = $options['setAttributes']; if ( isset($options['setDSNString']) ) $setDSNString = $options['setDSNString']; if ( isset($options['runInitCode']) ) $runInitCode = $options['runInitCode']; if ( isset($options['stringFetch']) ) $stringFetch = $options['stringFetch']; if ($connected) $this->isConnected = $connected; if ($setEncoding) $this->setEncoding(); if ($setAttributes) { $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION ); $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC ); $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $stringFetch ); } if ($runInitCode) { /* run initialisation query if any */ if ( $this->initSQL !== NULL ) { $this->Execute( $this->initSQL ); $this->initSQL = NULL; } if ( $this->initCode !== NULL ) { $code = $this->initCode; $code( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) ); } } if ($setDSNString) $this->dsn = $this->getDatabaseType(); } /** * @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 ); if ( empty( $rows ) || !is_array( $rows ) ) { return array(); } $cols = array(); foreach ( $rows as $row ) { $cols[] = reset( $row ); } return $cols; } /** * @see Driver::GetOne */ public function GetOne( $sql, $bindings = array() ) { $arr = $this->GetAll( $sql, $bindings ); if ( empty( $arr[0] ) || !is_array( $arr[0] ) ) { return NULL; } return reset( $arr[0] ); } /** * Alias for getOne(). * Backward compatibility. * * @param string $sql SQL * @param array $bindings bindings * * @return string|NULL */ 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 ); if ( is_array( $arr ) && count( $arr ) ) { return reset( $arr ); } return array(); } /** * @see Driver::Execute */ 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; } /** * @see Driver::setDebugMode */ 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. * * This method is for more fine-grained control. Normally * you should use the facade to start the query debugger for * you. The facade will manage the object wirings necessary * to use the debugging functionality. * * Usage (through facade): * * <code> * R::debug( TRUE ); * ...rest of program... * R::debug( FALSE ); * </code> * * The example above illustrates how to use the RedBeanPHP * query debugger through the facade. * * @param Logger $logger the logger instance to be used for logging * * @return self */ public function setLogger( Logger $logger ) { $this->logger = $logger; return $this; } /** * 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. Use this method to identify the current PDO driver * used to provide access to the database. Example of a database * driver string: * * <code> * mysql * </code> * * Usage: * * <code> * echo R::getDatabaseAdapter()->getDatabase()->getDatabaseType(); * </code> * * The example above prints the current database driver string to * stdout. * * Note that this is a driver-specific method, not part of the * driver interface. This method might not be available in other * drivers since it relies on PDO. * * @return string */ public function getDatabaseType() { $this->connect(); return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME ); } /** * Returns the version identifier string of the database client. * This method can be used to identify the currently installed * database client. Note that this method will also establish a connection * (because this is required to obtain the version information). * * Example of a version string: * * <code> * mysqlnd 5.0.12-dev - 20150407 - $Id: b5c5906d452ec590732a93b051f3827e02749b83 $ * </code> * * Usage: * * <code> * echo R::getDatabaseAdapter()->getDatabase()->getDatabaseVersion(); * </code> * * The example above will print the version string to stdout. * * Note that this is a driver-specific method, not part of the * driver interface. This method might not be available in other * drivers since it relies on PDO. * * To obtain the database server version, use getDatabaseServerVersion() * instead. * * @return mixed */ public function getDatabaseVersion() { $this->connect(); return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION ); } /** * Returns the underlying PHP PDO instance. * For some low-level database operations you'll need access to the PDO * object. Not that this method is only available in RPDO and other * PDO based database drivers for RedBeanPHP. Other drivers may not have * a method like this. The following example demonstrates how to obtain * a reference to the PDO instance from the facade: * * Usage: * * <code> * $pdo = R::getDatabaseAdapter()->getDatabase()->getPDO(); * </code> * * @return \PDO */ public function getPDO() { $this->connect(); return $this->pdo; } /** * Closes the database connection. * While database connections are closed automatically at the end of the PHP script, * closing database connections is generally recommended to improve performance. * Closing a database connection will immediately return the resources to PHP. * * Usage: * * <code> * R::setup( ... ); * ... do stuff ... * R::close(); * </code> * * @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; return $this; } /** * Resets the query counter. * The query counter can be used to monitor the number * of database queries that have * been processed according to the database driver. You can use this * to monitor the number of queries required to render a page. * * Usage: * * <code> * R::resetQueryCount(); * echo R::getQueryCount() . ' queries processed.'; * </code> * * @return self */ public function resetCounter() { $this->queryCounter = 0; return $this; } /** * Returns the number of SQL queries processed. * This method returns the number of database queries that have * been processed according to the database driver. You can use this * to monitor the number of queries required to render a page. * * Usage: * * <code> * echo R::getQueryCount() . ' queries processed.'; * </code> * * @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; } /** * Sets a query to be executed upon connecting to the database. * This method provides an opportunity to configure the connection * to a database through an SQL-based interface. Objects can provide * an SQL string to be executed upon establishing a connection to * the database. This has been used to solve issues with default * foreign key settings in SQLite3 for instance, see Github issues: * #545 and #548. * * @param string $sql SQL query to run upon connecting to database * * @return self */ public function setInitQuery( $sql ) { $this->initSQL = $sql; return $this; } /** * Returns the version string from the database server. * * @return string */ public function DatabaseServerVersion() { return trim( strval( $this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION) ) ); } }