From c13c425b44d183155130f4d12f214a3028b19d2d Mon Sep 17 00:00:00 2001 From: Lorenzo Pisani Date: Sat, 17 Aug 2013 15:40:51 -0700 Subject: [PATCH 01/10] Add MySQLi Database driver (refs #3898) --- classes/Database/MySQLi.php | 3 + classes/Database/MySQLi/Result.php | 3 + classes/Kohana/Database/MySQLi.php | 406 ++++++++++++++++++++++ classes/Kohana/Database/MySQLi/Result.php | 71 ++++ 4 files changed, 483 insertions(+) create mode 100644 classes/Database/MySQLi.php create mode 100644 classes/Database/MySQLi/Result.php create mode 100644 classes/Kohana/Database/MySQLi.php create mode 100644 classes/Kohana/Database/MySQLi/Result.php diff --git a/classes/Database/MySQLi.php b/classes/Database/MySQLi.php new file mode 100644 index 0000000..0e992b2 --- /dev/null +++ b/classes/Database/MySQLi.php @@ -0,0 +1,3 @@ +_connection) + return; + + if (Database_MySQLi::$_set_names === NULL) + { + // Determine if we can use mysqli_set_charset(), which is only + // available on PHP 5.2.3+ when compiled against MySQL 5.0+ + Database_MySQLi::$_set_names = ! function_exists('mysqli_set_charset'); + } + + // Extract the connection parameters, adding required variabels + extract($this->_config['connection'] + array( + 'database' => '', + 'hostname' => '', + 'username' => '', + 'password' => '', + 'socket' => '', + 'port' => 3306, + )); + + // Prevent this information from showing up in traces + unset($this->_config['connection']['username'], $this->_config['connection']['password']); + + try + { + $this->_connection = new mysqli($hostname, $username, $password, $database, $port, $socket); + } + catch (Exception $e) + { + // No connection exists + $this->_connection = NULL; + + throw new Database_Exception(':error', array(':error' => $e->getMessage()), $e->getCode()); + } + + // \xFF is a better delimiter, but the PHP driver uses underscore + $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); + + if ( ! empty($this->_config['charset'])) + { + // Set the character set + $this->set_charset($this->_config['charset']); + } + + if ( ! empty($this->_config['connection']['variables'])) + { + // Set session variables + $variables = array(); + + foreach ($this->_config['connection']['variables'] as $var => $val) + { + $variables[] = 'SESSION '.$var.' = '.$this->quote($val); + } + + $this->_connection->query('SET '.implode(', ', $variables)); + } + } + + public function disconnect() + { + try + { + // Database is assumed disconnected + $status = TRUE; + + if (is_resource($this->_connection)) + { + if ($status = $this->_connection->close()) + { + // Clear the connection + $this->_connection = NULL; + + // Clear the instance + parent::disconnect(); + } + } + } + catch (Exception $e) + { + // Database is probably not disconnected + $status = ! is_resource($this->_connection); + } + + return $status; + } + + public function set_charset($charset) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if (Database_MySQLi::$_set_names === TRUE) + { + // PHP is compiled against MySQL 4.x + $status = (bool) $this->_connection->query('SET NAMES '.$this->quote($charset)); + } + else + { + // PHP is compiled against MySQL 5.x + $status = $this->_connection->set_charset($charset); + } + + if ($status === FALSE) + { + throw new Database_Exception(':error', array(':error' => $this->_connection->error), $this->_connection->errno); + } + } + + public function query($type, $sql, $as_object = FALSE, array $params = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if (Kohana::$profiling) + { + // Benchmark this query for the current instance + $benchmark = Profiler::start("Database ({$this->_instance})", $sql); + } + + // Execute the query + if (($result = $this->_connection->query($sql)) === FALSE) + { + if (isset($benchmark)) + { + // This benchmark is worthless + Profiler::delete($benchmark); + } + + throw new Database_Exception(':error [ :query ]', array( + ':error' => $this->_connection->error, + ':query' => $sql + ), $this->_connection->errno); + } + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + // Set the last query + $this->last_query = $sql; + + if ($type === Database::SELECT) + { + // Return an iterator of results + return new Database_MySQLi_Result($result, $sql, $as_object, $params); + } + elseif ($type === Database::INSERT) + { + // Return a list of insert id and rows created + return array( + $this->_connection->insert_id, + $this->_connection->affected_rows, + ); + } + else + { + // Return the number of rows affected + return $this->_connection->affected_rows; + } + } + + public function datatype($type) + { + static $types = array + ( + 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'), + 'bool' => array('type' => 'bool'), + 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'), + 'datetime' => array('type' => 'string'), + 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), + 'double' => array('type' => 'float'), + 'double precision unsigned' => array('type' => 'float', 'min' => '0'), + 'double unsigned' => array('type' => 'float', 'min' => '0'), + 'enum' => array('type' => 'string'), + 'fixed' => array('type' => 'float', 'exact' => TRUE), + 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), + 'float unsigned' => array('type' => 'float', 'min' => '0'), + 'geometry' => array('type' => 'string', 'binary' => TRUE), + 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), + 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), + 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'), + 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'), + 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'), + 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'), + 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'), + 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'), + 'national varchar' => array('type' => 'string'), + 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), + 'nvarchar' => array('type' => 'string'), + 'point' => array('type' => 'string', 'binary' => TRUE), + 'real unsigned' => array('type' => 'float', 'min' => '0'), + 'set' => array('type' => 'string'), + 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'), + 'text' => array('type' => 'string', 'character_maximum_length' => '65535'), + 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'), + 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'), + 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'), + 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'), + 'year' => array('type' => 'string'), + ); + + $type = str_replace(' zerofill', '', $type); + + if (isset($types[$type])) + return $types[$type]; + + return parent::datatype($type); + } + + /** + * Start a SQL transaction + * + * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + * + * @param string $mode Isolation level + * @return boolean + */ + public function begin($mode = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if ($mode AND ! $this->_connection->query("SET TRANSACTION ISOLATION LEVEL $mode")) + { + throw new Database_Exception(':error', array( + ':error' => $this->_connection->error + ), $this->_connection->errno); + } + + return (bool) $this->_connection->query('START TRANSACTION'); + } + + /** + * Commit a SQL transaction + * + * @return boolean + */ + public function commit() + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return (bool) $this->_connection->query('COMMIT'); + } + + /** + * Rollback a SQL transaction + * + * @return boolean + */ + public function rollback() + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + return (bool) $this->_connection->query('ROLLBACK'); + } + + public function list_tables($like = NULL) + { + if (is_string($like)) + { + // Search for table names + $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); + } + else + { + // Find all table names + $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); + } + + $tables = array(); + foreach ($result as $row) + { + $tables[] = reset($row); + } + + return $tables; + } + + public function list_columns($table, $like = NULL, $add_prefix = TRUE) + { + // Quote the table name + $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; + + if (is_string($like)) + { + // Search for column names + $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); + } + else + { + // Find all column names + $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE); + } + + $count = 0; + $columns = array(); + foreach ($result as $row) + { + list($type, $length) = $this->_parse_type($row['Type']); + + $column = $this->datatype($type); + + $column['column_name'] = $row['Field']; + $column['column_default'] = $row['Default']; + $column['data_type'] = $type; + $column['is_nullable'] = ($row['Null'] == 'YES'); + $column['ordinal_position'] = ++$count; + + switch ($column['type']) + { + case 'float': + if (isset($length)) + { + list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); + } + break; + case 'int': + if (isset($length)) + { + // MySQL attribute + $column['display'] = $length; + } + break; + case 'string': + switch ($column['data_type']) + { + case 'binary': + case 'varbinary': + $column['character_maximum_length'] = $length; + break; + case 'char': + case 'varchar': + $column['character_maximum_length'] = $length; + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + $column['collation_name'] = $row['Collation']; + break; + case 'enum': + case 'set': + $column['collation_name'] = $row['Collation']; + $column['options'] = explode('\',\'', substr($length, 1, -1)); + break; + } + break; + } + + // MySQL attributes + $column['comment'] = $row['Comment']; + $column['extra'] = $row['Extra']; + $column['key'] = $row['Key']; + $column['privileges'] = $row['Privileges']; + + $columns[$row['Field']] = $column; + } + + return $columns; + } + + public function escape($value) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if (($value = $this->_connection->real_escape_string( (string) $value)) === FALSE) + { + throw new Database_Exception(':error', array( + ':error' => $this->_connection->error, + ), $this->_connection->errno); + } + + // SQL standard is to use single-quotes for all values + return "'$value'"; + } + +} // End Database_MySQLi diff --git a/classes/Kohana/Database/MySQLi/Result.php b/classes/Kohana/Database/MySQLi/Result.php new file mode 100644 index 0000000..4a12441 --- /dev/null +++ b/classes/Kohana/Database/MySQLi/Result.php @@ -0,0 +1,71 @@ +_total_rows = $result->num_rows; + } + + public function __destruct() + { + if (is_resource($this->_result)) + { + $this->_result->free(); + } + } + + public function seek($offset) + { + if ($this->offsetExists($offset) AND $this->_result->data_seek($offset)) + { + // Set the current row to the offset + $this->_current_row = $this->_internal_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + public function current() + { + if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) + return NULL; + + // Increment internal row for optimization assuming rows are fetched in order + $this->_internal_row++; + + if ($this->_as_object === TRUE) + { + // Return an stdClass + return $this->_result->fetch_object(); + } + elseif (is_string($this->_as_object)) + { + // Return an object of given class name + return $this->_result->fetch_object($this->_as_object, (array) $this->_object_params); + } + else + { + // Return an array of the row + return $this->_result->fetch_assoc(); + } + } + +} // End Database_MySQLi_Result_Select From 8ae83ac07593879f81fc2f6f3bd265eb0ed66512 Mon Sep 17 00:00:00 2001 From: Lorenzo Pisani Date: Sat, 17 Aug 2013 15:43:05 -0700 Subject: [PATCH 02/10] Oops, fix transparent extension (refs #3898) --- classes/Database/MySQLi.php | 2 +- classes/Database/MySQLi/Result.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/Database/MySQLi.php b/classes/Database/MySQLi.php index 0e992b2..df5773f 100644 --- a/classes/Database/MySQLi.php +++ b/classes/Database/MySQLi.php @@ -1,3 +1,3 @@ Date: Mon, 18 Aug 2014 12:29:36 -0500 Subject: [PATCH 03/10] classes -> src, update docblock --- {classes/Kohana => src}/Database/MySQLi.php | 9 +++++---- {classes/Kohana => src}/Database/MySQLi/Result.php | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) rename {classes/Kohana => src}/Database/MySQLi.php (98%) rename {classes/Kohana => src}/Database/MySQLi/Result.php (92%) diff --git a/classes/Kohana/Database/MySQLi.php b/src/Database/MySQLi.php similarity index 98% rename from classes/Kohana/Database/MySQLi.php rename to src/Database/MySQLi.php index 82ad4a0..54a6fce 100644 --- a/classes/Kohana/Database/MySQLi.php +++ b/src/Database/MySQLi.php @@ -1,14 +1,15 @@ - Date: Mon, 18 Aug 2014 12:31:40 -0500 Subject: [PATCH 04/10] remove empty extensions --- classes/Database/MySQLi.php | 3 --- classes/Database/MySQLi/Result.php | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 classes/Database/MySQLi.php delete mode 100644 classes/Database/MySQLi/Result.php diff --git a/classes/Database/MySQLi.php b/classes/Database/MySQLi.php deleted file mode 100644 index df5773f..0000000 --- a/classes/Database/MySQLi.php +++ /dev/null @@ -1,3 +0,0 @@ - Date: Mon, 18 Aug 2014 12:37:06 -0500 Subject: [PATCH 05/10] add phpunit to dev deps --- README.md | 14 ++++++++++++++ composer.json | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index afc0f7a..c17252a 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,17 @@ Ohanzee is a set of PHP components that are dependency free, PSR compatible, and well documented. The design is guided by SOLID, DRY, and KISS (the SDK) philosophies. Many were originally part of the Kohana PHP Framework. + +# Testing + +``` +composer install --dev +phpunit --bootstrap=vendor/autoload.php tests +``` + +to set the database host, user, or password: + +``` +PHPUNIT_TEST_HOSTNAME=myhostname PHPUNIT_TEST_USERNAME=bob PHPUNIT_TEST_PASSWORD=secret \ + phpunit --bootstrap=vendor/autoload.php tests +``` diff --git a/composer.json b/composer.json index 9fba9f7..569263c 100644 --- a/composer.json +++ b/composer.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "name": "ohanzee/database", "description": "Ohanzee component for database SQL queries and query building", "homepage": "http://ohanzee.org/", @@ -44,5 +45,8 @@ }, "autoload": { "psr-0": { "": "src/" } + }, + "require-dev": { + "phpunit/phpunit": "4.2.*" } } From 2141f449e227d0c4fd16e5bdad57d17266db1547 Mon Sep 17 00:00:00 2001 From: Woody Gilk Date: Mon, 18 Aug 2014 13:14:25 -0500 Subject: [PATCH 06/10] remove mysql, it is deprecated --- src/Database/MySQL.php | 444 ---------------------------------- src/Database/MySQL/Result.php | 98 -------- 2 files changed, 542 deletions(-) delete mode 100644 src/Database/MySQL.php delete mode 100644 src/Database/MySQL/Result.php diff --git a/src/Database/MySQL.php b/src/Database/MySQL.php deleted file mode 100644 index 86230c6..0000000 --- a/src/Database/MySQL.php +++ /dev/null @@ -1,444 +0,0 @@ -_connection) - return; - - if (Database_MySQL::$_set_names === NULL) - { - // Determine if we can use mysql_set_charset(), which is only - // available on PHP 5.2.3+ when compiled against MySQL 5.0+ - Database_MySQL::$_set_names = ! function_exists('mysql_set_charset'); - } - - // Extract the connection parameters, adding required variabels - extract($this->_config['connection'] + array( - 'database' => '', - 'hostname' => '', - 'username' => '', - 'password' => '', - 'persistent' => FALSE, - )); - - // Prevent this information from showing up in traces - unset($this->_config['connection']['username'], $this->_config['connection']['password']); - - try - { - if ($persistent) - { - // Create a persistent connection - $this->_connection = mysql_pconnect($hostname, $username, $password); - } - else - { - // Create a connection and force it to be a new link - $this->_connection = mysql_connect($hostname, $username, $password, TRUE); - } - } - catch (Exception $e) - { - // No connection exists - $this->_connection = NULL; - - throw new Database_Exception(':error', - array(':error' => $e->getMessage()), - $e->getCode()); - } - - // \xFF is a better delimiter, but the PHP driver uses underscore - $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); - - $this->_select_db($database); - - if ( ! empty($this->_config['charset'])) - { - // Set the character set - $this->set_charset($this->_config['charset']); - } - - if ( ! empty($this->_config['connection']['variables'])) - { - // Set session variables - $variables = array(); - - foreach ($this->_config['connection']['variables'] as $var => $val) - { - $variables[] = 'SESSION '.$var.' = '.$this->quote($val); - } - - mysql_query('SET '.implode(', ', $variables), $this->_connection); - } - } - - /** - * Select the database - * - * @param string $database Database - * @return void - */ - protected function _select_db($database) - { - if ( ! mysql_select_db($database, $this->_connection)) - { - // Unable to select database - throw new Database_Exception(':error', - array(':error' => mysql_error($this->_connection)), - mysql_errno($this->_connection)); - } - - Database_MySQL::$_current_databases[$this->_connection_id] = $database; - } - - public function disconnect() - { - try - { - // Database is assumed disconnected - $status = TRUE; - - if (is_resource($this->_connection)) - { - if ($status = mysql_close($this->_connection)) - { - // Clear the connection - $this->_connection = NULL; - - // Clear the instance - parent::disconnect(); - } - } - } - catch (Exception $e) - { - // Database is probably not disconnected - $status = ! is_resource($this->_connection); - } - - return $status; - } - - public function set_charset($charset) - { - // Make sure the database is connected - $this->_connection or $this->connect(); - - if (Database_MySQL::$_set_names === TRUE) - { - // PHP is compiled against MySQL 4.x - $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->_connection); - } - else - { - // PHP is compiled against MySQL 5.x - $status = mysql_set_charset($charset, $this->_connection); - } - - if ($status === FALSE) - { - throw new Database_Exception(':error', - array(':error' => mysql_error($this->_connection)), - mysql_errno($this->_connection)); - } - } - - public function query($type, $sql, $as_object = FALSE, array $params = NULL) - { - // Make sure the database is connected - $this->_connection or $this->connect(); - - if (Kohana::$profiling) - { - // Benchmark this query for the current instance - $benchmark = Profiler::start("Database ({$this->_instance})", $sql); - } - - if ( ! empty($this->_config['connection']['persistent']) AND $this->_config['connection']['database'] !== Database_MySQL::$_current_databases[$this->_connection_id]) - { - // Select database on persistent connections - $this->_select_db($this->_config['connection']['database']); - } - - // Execute the query - if (($result = mysql_query($sql, $this->_connection)) === FALSE) - { - if (isset($benchmark)) - { - // This benchmark is worthless - Profiler::delete($benchmark); - } - - throw new Database_Exception(':error [ :query ]', - array(':error' => mysql_error($this->_connection), ':query' => $sql), - mysql_errno($this->_connection)); - } - - if (isset($benchmark)) - { - Profiler::stop($benchmark); - } - - // Set the last query - $this->last_query = $sql; - - if ($type === Database::SELECT) - { - // Return an iterator of results - return new Database_MySQL_Result($result, $sql, $as_object, $params); - } - elseif ($type === Database::INSERT) - { - // Return a list of insert id and rows created - return array( - mysql_insert_id($this->_connection), - mysql_affected_rows($this->_connection), - ); - } - else - { - // Return the number of rows affected - return mysql_affected_rows($this->_connection); - } - } - - public function datatype($type) - { - static $types = array - ( - 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'), - 'bool' => array('type' => 'bool'), - 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'), - 'datetime' => array('type' => 'string'), - 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), - 'double' => array('type' => 'float'), - 'double precision unsigned' => array('type' => 'float', 'min' => '0'), - 'double unsigned' => array('type' => 'float', 'min' => '0'), - 'enum' => array('type' => 'string'), - 'fixed' => array('type' => 'float', 'exact' => TRUE), - 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), - 'float unsigned' => array('type' => 'float', 'min' => '0'), - 'geometry' => array('type' => 'string', 'binary' => TRUE), - 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), - 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), - 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'), - 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'), - 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'), - 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'), - 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'), - 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'), - 'national varchar' => array('type' => 'string'), - 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), - 'nvarchar' => array('type' => 'string'), - 'point' => array('type' => 'string', 'binary' => TRUE), - 'real unsigned' => array('type' => 'float', 'min' => '0'), - 'set' => array('type' => 'string'), - 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'), - 'text' => array('type' => 'string', 'character_maximum_length' => '65535'), - 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'), - 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'), - 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'), - 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'), - 'year' => array('type' => 'string'), - ); - - $type = str_replace(' zerofill', '', $type); - - if (isset($types[$type])) - return $types[$type]; - - return parent::datatype($type); - } - - /** - * Start a SQL transaction - * - * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html - * - * @param string $mode Isolation level - * @return boolean - */ - public function begin($mode = NULL) - { - // Make sure the database is connected - $this->_connection or $this->connect(); - - if ($mode AND ! mysql_query("SET TRANSACTION ISOLATION LEVEL $mode", $this->_connection)) - { - throw new Database_Exception(':error', - array(':error' => mysql_error($this->_connection)), - mysql_errno($this->_connection)); - } - - return (bool) mysql_query('START TRANSACTION', $this->_connection); - } - - /** - * Commit a SQL transaction - * - * @return boolean - */ - public function commit() - { - // Make sure the database is connected - $this->_connection or $this->connect(); - - return (bool) mysql_query('COMMIT', $this->_connection); - } - - /** - * Rollback a SQL transaction - * - * @return boolean - */ - public function rollback() - { - // Make sure the database is connected - $this->_connection or $this->connect(); - - return (bool) mysql_query('ROLLBACK', $this->_connection); - } - - public function list_tables($like = NULL) - { - if (is_string($like)) - { - // Search for table names - $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); - } - else - { - // Find all table names - $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); - } - - $tables = array(); - foreach ($result as $row) - { - $tables[] = reset($row); - } - - return $tables; - } - - public function list_columns($table, $like = NULL, $add_prefix = TRUE) - { - // Quote the table name - $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; - - if (is_string($like)) - { - // Search for column names - $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); - } - else - { - // Find all column names - $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE); - } - - $count = 0; - $columns = array(); - foreach ($result as $row) - { - list($type, $length) = $this->_parse_type($row['Type']); - - $column = $this->datatype($type); - - $column['column_name'] = $row['Field']; - $column['column_default'] = $row['Default']; - $column['data_type'] = $type; - $column['is_nullable'] = ($row['Null'] == 'YES'); - $column['ordinal_position'] = ++$count; - - switch ($column['type']) - { - case 'float': - if (isset($length)) - { - list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); - } - break; - case 'int': - if (isset($length)) - { - // MySQL attribute - $column['display'] = $length; - } - break; - case 'string': - switch ($column['data_type']) - { - case 'binary': - case 'varbinary': - $column['character_maximum_length'] = $length; - break; - case 'char': - case 'varchar': - $column['character_maximum_length'] = $length; - case 'text': - case 'tinytext': - case 'mediumtext': - case 'longtext': - $column['collation_name'] = $row['Collation']; - break; - case 'enum': - case 'set': - $column['collation_name'] = $row['Collation']; - $column['options'] = explode('\',\'', substr($length, 1, -1)); - break; - } - break; - } - - // MySQL attributes - $column['comment'] = $row['Comment']; - $column['extra'] = $row['Extra']; - $column['key'] = $row['Key']; - $column['privileges'] = $row['Privileges']; - - $columns[$row['Field']] = $column; - } - - return $columns; - } - - public function escape($value) - { - // Make sure the database is connected - $this->_connection or $this->connect(); - - if (($value = mysql_real_escape_string( (string) $value, $this->_connection)) === FALSE) - { - throw new Database_Exception(':error', - array(':error' => mysql_error($this->_connection)), - mysql_errno($this->_connection)); - } - - // SQL standard is to use single-quotes for all values - return "'$value'"; - } - -} // End Database_MySQL diff --git a/src/Database/MySQL/Result.php b/src/Database/MySQL/Result.php deleted file mode 100644 index 53d48b6..0000000 --- a/src/Database/MySQL/Result.php +++ /dev/null @@ -1,98 +0,0 @@ -_total_rows = mysql_num_rows($result); - } - - public function __destruct() - { - if (is_resource($this->_result)) - { - mysql_free_result($this->_result); - } - } - - public function seek($offset) - { - if ($this->offsetExists($offset) AND mysql_data_seek($this->_result, $offset)) - { - // Set the current row to the offset - $this->_current_row = $this->_internal_row = $offset; - - return TRUE; - } - else - { - return FALSE; - } - } - - public function current() - { - if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) - return NULL; - - // Increment internal row for optimization assuming rows are fetched in order - $this->_internal_row++; - - // FIXME mysql_fetch_object has been deprecated as of php 5.5! - // Please use mysqli_fetch_object or PDOStatement::fetch(PDO::FETCH_OBJ) instead. - - if ($this->_as_object === TRUE) - { - // Return an stdClass - return mysql_fetch_object($this->_result); - } - elseif (is_string($this->_as_object)) - { - /* The second and third argument for mysql_fetch_object are optional, but do - * not have default values defined. Passing _object_params with a non-array value results - * in undefined behavior that varies by PHP version. For example, if NULL is supplied on - * PHP 5.3, the resulting behavior is identical to calling with array(), which results in the - * classes __construct function being called with no arguments. This is only an issue when - * the _as_object class does not have an explicit __construct method resulting in the - * cryptic error "Class %s does not have a constructor hence you cannot use ctor_params." - * In contrast, the same function call on PHP 5.5 will 'functionally' interpret - * _object_params == NULL as an omission of the third argument, resulting in the original - * intended functionally. - * - * Because the backing code for the mysql_fetch_object has not changed between 5.3 and 5.5, - * I suspect this discrepancy is due to the way the classes are instantiated on a boarder - * level. Additionally, mysql_fetch_object has been deprecated in 5.5 and should probably be - * replaced by mysqli_fetch_object or PDOStatement::fetch(PDO::FETCH_OBJ) in Kohana 3.4. - */ - if ($this->_object_params !== NULL) - { - // Return an object of given class name with constructor params - return mysql_fetch_object($this->_result, $this->_as_object, $this->_object_params); - } - else - { - // Return an object of given class name without constructor params - return mysql_fetch_object($this->_result, $this->_as_object); - } - } - else - { - // Return an array of the row - return mysql_fetch_assoc($this->_result); - } - } - -} // End Database_MySQL_Result_Select From be1c31bb9dbb93788b3a4955cc65cc962b2de470 Mon Sep 17 00:00:00 2001 From: Woody Gilk Date: Mon, 18 Aug 2014 13:26:42 -0500 Subject: [PATCH 07/10] test mysqli connection --- tests/Database/MysqliTest.php | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/Database/MysqliTest.php diff --git a/tests/Database/MysqliTest.php b/tests/Database/MysqliTest.php new file mode 100644 index 0000000..efe43ff --- /dev/null +++ b/tests/Database/MysqliTest.php @@ -0,0 +1,43 @@ + @$_ENV['PHPUNIT_TEST_HOSTNAME'], + 'database' => @$_ENV['PHPUNIT_TEST_DATABASE'], + 'username' => @$_ENV['PHPUNIT_TEST_USERNAME'], + 'password' => @$_ENV['PHPUNIT_TEST_PASSWORD'], + ]; + + return array_filter($config); + } + + public function providerConfig() + { + return [ + [ + 'test', [ + 'charset' => 'utf8', + 'connection' => $this->getEnvConfig() + [ + 'hostname' => 'localhost', + 'database' => 'ohanzee', + 'username' => 'ohanzee', + 'password' => false, + ], + ] + ], + ]; + } + + /** + * @dataProvider providerConfig + */ + public function testCanConnect($name, $config) + { + $db = new Database_MySQLi($name, $config); + + $this->assertEquals(true, $db->connect()); + } +} From 3018dcfe7c326f092a7b83132aa60eac9d88086d Mon Sep 17 00:00:00 2001 From: Woody Gilk Date: Sat, 8 Nov 2014 12:21:33 -0600 Subject: [PATCH 08/10] fix bad rebase --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 569263c..42d1d39 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,4 @@ { -<<<<<<< HEAD "name": "ohanzee/database", "description": "Ohanzee component for database SQL queries and query building", "homepage": "http://ohanzee.org/", From b31d14d3640cdcc549dfb13a9ef1b6bdfeecc242 Mon Sep 17 00:00:00 2001 From: Woody Gilk Date: Sat, 8 Nov 2014 12:23:51 -0600 Subject: [PATCH 09/10] default connection type is mysqli --- config/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/database.php b/config/database.php index e2021bd..e874598 100644 --- a/config/database.php +++ b/config/database.php @@ -4,7 +4,7 @@ ( 'default' => array ( - 'type' => 'MySQL', + 'type' => 'MySQLi', 'connection' => array( /** * The following options are available for MySQL: From 552e62f8a05dbe21ef5b77273f732dfe14706459 Mon Sep 17 00:00:00 2001 From: Woody Gilk Date: Sat, 8 Nov 2014 12:35:19 -0600 Subject: [PATCH 10/10] add php.env for loading $_ENV config --- .gitignore | 3 +++ README.md | 22 ++++++++++++++++++++-- composer.json | 3 ++- tests/Database/MysqliTest.php | 13 +++++++++---- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 08a3795..b9795a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # Composer directory vendor/ +# $_ENV config overloading +.env + # This is a library composer.lock diff --git a/README.md b/README.md index c17252a..e46f885 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,27 @@ composer install --dev phpunit --bootstrap=vendor/autoload.php tests ``` -to set the database host, user, or password: +The default database connection details are: + +- hostname: localhost +- database: ohanzee +- username: ohanzee +- password: (empty) + +These values can be modified by creating a `.env` file in the root of the project: + +```ini +PHPUNIT_TEST_HOSTNAME=server +PHPUNIT_TEST_DATABASE=testing +PHPUNIT_TEST_USERNAME=jane +PHPUNIT_TEST_PASSWORD=likesblue +``` + +The `.env` file is a standard ini file and will be loaded using [dotenv](https://github.com/vlucas/phpdotenv). + +Any of the parameters can also be passed directly on the commandline: ``` -PHPUNIT_TEST_HOSTNAME=myhostname PHPUNIT_TEST_USERNAME=bob PHPUNIT_TEST_PASSWORD=secret \ +PHPUNIT_TEST_USERNAME=joe PHPUNIT_TEST_PASSWORD=likesgreen \ phpunit --bootstrap=vendor/autoload.php tests ``` diff --git a/composer.json b/composer.json index 42d1d39..0a2c840 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "psr-0": { "": "src/" } }, "require-dev": { - "phpunit/phpunit": "4.2.*" + "phpunit/phpunit": "4.2.*", + "vlucas/phpdotenv": "~1.0" } } diff --git a/tests/Database/MysqliTest.php b/tests/Database/MysqliTest.php index efe43ff..ef329f3 100644 --- a/tests/Database/MysqliTest.php +++ b/tests/Database/MysqliTest.php @@ -2,13 +2,18 @@ class MysqliTest extends PHPUnit_Framework_TestCase { + public function setUp() + { + Dotenv::load(realpath(__DIR__ . '/../../')); + } + private function getEnvConfig() { $config = [ - 'hostname' => @$_ENV['PHPUNIT_TEST_HOSTNAME'], - 'database' => @$_ENV['PHPUNIT_TEST_DATABASE'], - 'username' => @$_ENV['PHPUNIT_TEST_USERNAME'], - 'password' => @$_ENV['PHPUNIT_TEST_PASSWORD'], + 'hostname' => getenv('PHPUNIT_TEST_HOSTNAME'), + 'database' => getenv('PHPUNIT_TEST_DATABASE'), + 'username' => getenv('PHPUNIT_TEST_USERNAME'), + 'password' => getenv('PHPUNIT_TEST_PASSWORD'), ]; return array_filter($config);