From 12883cfbe25a2ed1c858ad04a814e2eaa2ef3dc4 Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Wed, 11 Jul 2012 14:59:20 +0000 Subject: [PATCH] Created class for work with database and test class for it --- .../tests/webim/libs/DatabaseTest.php | 204 ++++++++++ src/messenger/webim/libs/database.php | 368 ++++++++++++++++++ 2 files changed, 572 insertions(+) create mode 100644 src/messenger/tests/webim/libs/DatabaseTest.php create mode 100644 src/messenger/webim/libs/database.php diff --git a/src/messenger/tests/webim/libs/DatabaseTest.php b/src/messenger/tests/webim/libs/DatabaseTest.php new file mode 100644 index 00000000..7f123396 --- /dev/null +++ b/src/messenger/tests/webim/libs/DatabaseTest.php @@ -0,0 +1,204 @@ +object = Database::getInstance(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() { + $this->object->__destruct(); + } + + public static function setUpBeforeClass() { + global $mysqlhost, $mysqllogin, $mysqlpass, $mysqldb; + $dbh = new PDO( + "mysql:host={$mysqlhost};dbname={$mysqldb}", + $mysqllogin, + $mysqlpass + ); + $dbh->exec( + "CREATE TABLE phpunit_test_only " . + "(id INT (10) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (id))" + ); + $dbh = NULL; + } + + public static function tearDownAfterClass() { + global $mysqlhost, $mysqllogin, $mysqlpass, $mysqldb; + $dbh = new PDO( + "mysql:host={$mysqlhost};dbname={$mysqldb}", + $mysqllogin, + $mysqlpass + ); + $dbh->exec("DROP TABLE phpunit_test_only"); + $dbh = NULL; + } + + public function testGetInstance() { + $anotherDatabaseInstance = Database::getInstance(); + $this->assertSame($this->object, $anotherDatabaseInstance); + $anotherDatabaseInstance = NULL; + } + + public function testQuery() { + global $mysqlprefix; + + // Test simple good query + $this->assertTrue($this->object->query("SELECT 'test_value'")); + + // Test various fetch type + $result = $this->object->query( + "SELECT 'test_value_one' AS field_name", + NULL, + array('return_rows' => Database::RETURN_ONE_ROW) + ); + $this->assertEquals('test_value_one', $result['field_name']); + + $result = $this->object->query( + "SELECT 'test_value_two' AS field_name", + NULL, + array( + 'return_rows' => Database::RETURN_ONE_ROW, + 'fetch_type' => Database::FETCH_ASSOC + ) + ); + $this->assertEquals('test_value_two', $result['field_name']); + + $result = $this->object->query( + "SELECT 'test_value_four' AS field_name", + NULL, + array( + 'return_rows' => Database::RETURN_ONE_ROW, + 'fetch_type' => Database::FETCH_NUM + ) + ); + $this->assertEquals('test_value_four', $result[0]); + + $result = $this->object->query( + "SELECT 'test_value_four' AS field_name", + NULL, + array( + 'return_rows' => Database::RETURN_ONE_ROW, + 'fetch_type' => Database::FETCH_BOTH + ) + ); + $this->assertEquals('test_value_four', $result['field_name']); + $this->assertEquals('test_value_four', $result[0]); + + // Test all rows return + $result = $this->object->query( + "SELECT 'test_value_five' AS field_name " . + "UNION SELECT 'test_value_six' AS field_name", + NULL, + array('return_rows' => Database::RETURN_ALL_ROWS) + ); + $this->assertEquals('test_value_five', $result[0]['field_name']); + $this->assertEquals('test_value_six', $result[1]['field_name']); + + // Test unnamed placeholders + $result = $this->object->query( + "SELECT ? AS field_name ", + array('test_value_seven'), + array('return_rows' => Database::RETURN_ONE_ROW) + ); + $this->assertEquals('test_value_seven', $result['field_name']); + + // Test named placeholders + $result = $this->object->query( + "SELECT :name AS field_name ", + array(':name' => 'test_value_eight'), + array('return_rows' => Database::RETURN_ONE_ROW) + ); + $this->assertEquals('test_value_eight', $result['field_name']); + + // Test prefixies + $result = $this->object->query( + "SELECT '{test}' AS field_name ", + NULL, + array('return_rows' => Database::RETURN_ONE_ROW) + ); + $this->assertEquals($mysqlprefix.'test', $result['field_name']); + } + + public function testErrorInfo() { + $this->object->throwExeptions(true); + $this->assertFalse($this->object->errorInfo()); + try{ + $this->object->query("SOME_FAKE_QUERY"); + $this->fail('Exception must be thrown!'); + } catch(Exception $e) { + $errorInfo = $this->object->errorInfo(); + $this->assertEquals('42000', $errorInfo[0]); + $this->assertEquals(1064, $errorInfo[1]); + } + $this->object->query("SELECT 'test_value'"); + $errorInfo = $this->object->errorInfo(); + $this->assertEquals('00000', $errorInfo[0]); + } + + public function testInsertedId() { + $this->object->query("INSERT INTO phpunit_test_only (id) VALUES (NULL)"); + $actual_id = $this->object->insertedId(); + list($expected_id) = $this->object->query( + "SELECT MAX(id) FROM phpunit_test_only", + NULL, + array( + 'return_rows' => Database::RETURN_ONE_ROW, + 'fetch_type' => Database::FETCH_NUM + ) + ); + $this->assertTrue(is_numeric($actual_id)); + $this->assertEquals($expected_id, $actual_id); + } + + public function testAffectedRows() { + // Test on INSERT + $this->object->query( + "INSERT INTO phpunit_test_only (id) VALUES " . + "(100), (101), (102), (103), (104), (105)" + ); + $this->assertEquals(6, $this->object->affectedRows()); + + // Test on UPDATE + $this->object->query( + "UPDATE phpunit_test_only SET id = id+100 WHERE id > 103" + ); + $this->assertEquals(2, $this->object->affectedRows()); + + // Test on SELECT + $this->object->query( + "SELECT * FROM phpunit_test_only WHERE id >= 100 AND id <= 103" + ); + $this->assertEquals(4, $this->object->affectedRows()); + + // Test on DELETE + $this->object->query( + "DELETE FROM phpunit_test_only WHERE id >= 100" + ); + $this->assertEquals(6, $this->object->affectedRows()); + } + +} + +?> diff --git a/src/messenger/webim/libs/database.php b/src/messenger/webim/libs/database.php new file mode 100644 index 00000000..ea87f280 --- /dev/null +++ b/src/messenger/webim/libs/database.php @@ -0,0 +1,368 @@ +dbHost = $mysqlhost; + $this->dbLogin = $mysqllogin; + $this->dbPass = $mysqlpass; + $this->dbName = $mysqldb; + $this->dbEncoding = $dbencoding; + $this->tablesPrefix = $mysqlprefix; + $this->forceCharsetInConnection = $force_charset_in_connection; + $this->usePersistentConnection = $use_persistent_connection; + + // Create PDO object + $this->dbh = new PDO( + "mysql:host={$this->dbHost};dbname={$this->dbName}", + $this->dbLogin, + $this->dbPass + ); + + if ($this->forceCharsetInConnection) { + $this->dbh->exec("SET NAMES ".$this->dbh->quote($this->dbEncoding)); + } + + } catch(Exception $e) { + $this->handleError($e); + } + } + + /** + * Handles errors + * @param Exception $e + */ + protected function handleError(Exception $e){ + if ($this->throwExceptions) { + throw $e; + } + die($e->getMessage()); + } + + /** + * Set if exceptions must be process into the class or thrown. + * @param boolean $value + */ + public function throwExeptions($value){ + $this->throwExceptions = $value; + } + + /** + * Database class destructor. + */ + public function __destruct(){ + foreach($this->preparedStatements as $key => $statement) { + $this->preparedStatements[$key] = NULL; + } + $this->dbh = NULL; + self::$instance = NULL; + } + + /** + * Executes SQL query. + * In SQL query can be used PDO style placeholders: + * unnamed placeholders (question marks '?') and named placeholders (like + * ':name'). + * If unnamed placeholders are used, $values array must have numeric indexes. + * If named placeholders are used, $values param must be an associative array + * with keys corresponding to the placeholders names + * + * Table prefix automatically substitute if table name puts in curly braces + * + * @param string $query SQL query + * @param array $values Values, that must be substitute instead of + * placeholders in SQL query. + * @param array $params Array of query parameters. It can contains values with + * following keys: + * - 'return_rows' control if rows must be returned and how many rows must + * be returnd. The value can be Database::RETURN_ONE_ROW for olny one row + * or Database::RETURN_ALL_ROWS for all rows. If this key not specified, + * the function will not return any rows. + * - 'fetch_type' control indexes in resulting rows. The value can be + * Database::FETCH_ASSOC for associative array, Database::FETCH_NUM for + * array with numeric indexes and Database::FETCH_BOTH for both indexes. + * Default value is Database::FETCH_ASSOC. + * @return mixed If 'return_rows' key of the $params array is specified, + * returns one or several rows (depending on $params['return_rows'] value) or + * boolean false on fail. + * If 'return_rows' key of the $params array is not specified, returns + * boolean true on success or false on fail. + * + * @see Database::RETURN_ONE_ROW + * @see Database::RETURN_ALL_ROWS + * @see Database::FETCH_ASSOC + * @see Database::FETCH_NUM + * @see Database::FETCH_BOTH + */ + public function query($query, $values = NULL, $params = array()){ + try{ + $query = preg_replace("/\{(\S+)\}/", $this->tablesPrefix."$1", $query); + + $query_key = md5($query); + if (! array_key_exists($query_key, $this->preparedStatements)) { + $this->preparedStatements[$query_key] = $this->dbh->prepare($query); + } + + $this->lastQuery = $query_key; + + // Execute query + $this->preparedStatements[$query_key]->execute($values); + + // Check if error occurs + if ($this->preparedStatements[$query_key]->errorCode() !== '00000') { + $errorInfo = $this->preparedStatements[$query_key]->errorInfo(); + throw new Exception(' Query failed: ' . $errorInfo[2]); + } + + // No need to return rows + if (! array_key_exists('return_rows', $params)) { + return true; + } + + // Some rows must be returned + + // Get indexes type + if (! array_key_exists('fetch_type', $params)) { + $params['fetch_type'] = Database::FETCH_ASSOC; + } + switch($params['fetch_type']){ + case Database::FETCH_NUM: + $fetch_type = PDO::FETCH_NUM; + break; + case Database::FETCH_ASSOC: + $fetch_type = PDO::FETCH_ASSOC; + break; + case Database::FETCH_BOTH: + $fetch_type = PDO::FETCH_BOTH; + break; + default: + throw new Exception("Unknown 'fetch_type' value!"); + } + + // Get results + $rows = array(); + if ($params['return_rows'] == Database::RETURN_ONE_ROW) { + $rows = $this->preparedStatements[$query_key]->fetch($fetch_type); + } elseif ($params['return_rows'] == Database::RETURN_ALL_ROWS) { + $rows = $this->preparedStatements[$query_key]->fetchAll($fetch_type); + } else { + throw new Exception("Unknown 'return_rows' value!"); + } + $this->preparedStatements[$query_key]->closeCursor(); + + return $rows; + } catch(Exception $e) { + $this->handleError($e); + } + } + + /** + * Returns value of PDOStatement::$errorInfo property for last query + * @return string Error info array + * + * @see PDOStatement::$erorrInfo + */ + public function errorInfo(){ + if (is_null($this->lastQuery)) { + return false; + } + try{ + $errorInfo = $this->preparedStatements[$this->lastQuery]->errorInfo(); + } catch (Exception $e) { + $this->handleError($e); + } + return $errorInfo; + } + + /** + * Returns the ID of the last inserted row + * + * @return int The ID + */ + public function insertedId(){ + try{ + $lastInsertedId = $this->dbh->lastInsertId(); + } catch(Exception $e) { + $this->handleError($e); + } + return $lastInsertedId; + } + + /** + * Get count of affected rows in the last query + * + * @return int Affected rows count + */ + public function affectedRows(){ + if (is_null($this->lastQuery)) { + return false; + } + try{ + $affected_rows = $this->preparedStatements[$this->lastQuery]->rowCount(); + } catch(Exception $e) { + $this->handleError($e); + } + return $affected_rows; + } + +} + +?> \ No newline at end of file