Commit e99f9df3 authored by Carsten  Rose's avatar Carsten Rose
Browse files

Database*.php: added Database class

parent 8f76c009
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 1/4/16
* Time: 7:14 PM
*/
namespace qfq;
use qfq;
use qfq\store;
use qfq\exceptions;
use qfq\exceptions\CodeException;
use qfq\exceptions\UserException;
use qfq\exceptions\DbException;
require_once(__DIR__ . '/../qfq/exceptions/UserException.php');
require_once(__DIR__ . '/../qfq/exceptions/CodeException.php');
require_once(__DIR__ . '/../qfq/exceptions/DbException.php');
require_once(__DIR__ . '/../qfq/store/Store.php');
class Database {
/**
* @var \PDO
*/
public $pdo = null;
/**
* @var store\Store
*/
private $store = null;
private $stmt = null;
/**
* Returns current data base handle from Store[System][SYSTEM_DBH].
* If not exists: open database and store the new dbh in Store[System][SYSTEM_DBH]
*
* @throws CodeException
* @throws UserException
*/
public function __construct() {
$this->store = \qfq\store\Store::getInstance();
$this->pdo = $this->store->getVar(SYSTEM_DBH, STORE_SYSTEM);
if ($this->pdo === false) {
$this->pdo = $this->dbConnect();
$this->store->setVar(SYSTEM_DBH, $this->pdo, STORE_SYSTEM);
}
}
/**
* If not
* @return \PDO
* @throws UserException
*/
private function dbConnect() {
$dbuser = $this->store->getVar(SYSTEM_DBUSER, STORE_SYSTEM);
$dbserver = $this->store->getVar(SYSTEM_DBSERVER, STORE_SYSTEM);
$dbpw = $this->store->getVar(SYSTEM_DBPW, STORE_SYSTEM);
$db = $this->store->getVar(SYSTEM_DB, STORE_SYSTEM);
try {
$pdo = new \PDO("mysql:host=" . $dbserver . ";dbname=" . $db, $dbuser, $dbpw, array(\PDO::ATTR_PERSISTENT => true));
} catch (\Exception $e) {
throw new UserException ("Error open Database 'mysql:host=" . $dbserver . ";dbname=" . $db . ";dbuser=" . $dbuser . "'': " . $e->getMessage(), ERROR_OPEN_DATABASE);
}
return $pdo;
}
/**
* Return the number of rows returned by the last call to execute().
*
* If execute() has never been called, returns FALSE.
*
* @return mixed Number of rows returned by last call to execute(). If Database::execute()
* has never been called prior a call to this method, false is returned.
*/
public function rowCount() {
if ($this->stmt == null) {
return false;
}
return $this->stmt->rowCount();
}
/**
* Get the values for a given ENUM or SET column
*
* @param string $table name of the table
* @param string $columnName name of the column
*
* @throws exceptions\UserException if the table or column does not exist, or is not of type ENUM or SET
* @return array
*/
public function getSetEnumValueList($table, $columnName) {
$columnDefinition = $this->getFieldDefinitionFromTable($table, $columnName);
$setEnumDefinition = $columnDefinition["Type"];
// $setEnumDefinition holds now a string like
// String: enum('','red','blue','green')
$len = mb_strlen($setEnumDefinition);
# "enum('" = 6, "set('" = 5
$tokenLength = strpos($setEnumDefinition, "'") + 1;
// count("enum('") == 6, count("')") == 2
$setEnumString = mb_substr($setEnumDefinition, $tokenLength, $len - (2 + $tokenLength));
// String: ','red','blue','green
if (($setEnumValueList = explode("','", $setEnumString)) === false) {
return array();
}
return $setEnumValueList;
}
/**
* Get database column definition.
*
* If the column is not found in the table, an exception is thrown.
*
* @param string $table name of the table
*
* @param string $columnName name of the column
*
* @return array the definition of the column as retrieved by Database::getFieldsFromTable().
*
* @throws exceptions\UserException
*
*/
private function getFieldDefinitionFromTable($table, $columnName) {
$tableDefinition = $this->getFieldsFromTable($table);
foreach ($tableDefinition AS $row) {
if ($row["Field"] == $columnName) {
return $row;
}
}
throw new DbException("Column name '$columnName' not found in table '$table'.");
}
/**
* Get all column definitions for a table
*
* @param string $table table to retrieve column definition from
*
* @return array column definition of table as returned by SHOW FIELDS FROM as associative array.
*/
private function getFieldsFromTable($table) {
$statement = $this->pdo->query("SHOW FIELDS FROM `$table`");
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @return mixed
*/
public function getLastInsertId() {
return $this->pdo->lastInsertId();
}
/**
* Fires query $sql and fetches result als assoc array.
* $mode
* ROW_REGULAR return result set as assoc array. No check about number of rows.
* ROW_IMPLODE_ALL return result set as one string: implode all rows and columns. No check about number of rows.
* ROW_EXACT_1 return result set as assoc array. It has to be exact one row selected. If not: throw Exception,
* ROW_EMPTY_IS_OK return result set as assoc array. It has to be 0 or one row selected. If not: throw Exception,
*
* @param $sql
* @param $mode
*/
public function querySimple($sql, $mode = ROW_REGULAR) {
// for error reporting in exception
$count = $this->execute($sql, array());
if ($count === false) {
throw new DbException("No idea why this error happens - please take some time and check this: $sql", ERROR_DB_GENERIC_CHECK);
}
if ($mode === ROW_IMPLODE_ALL) {
return $this->fetchAll(ROW_IMPLODE_ALL);
}
if ($mode === ROW_REGULAR) {
return $this->fetchAll();
}
if ($count < 1) {
if ($mode === ROW_EMPTY_IS_OK) {
return array();
}
throw new DbException("Expected one row, got nothing: $sql", ERROR_DB_TOO_FEW_ROWS);
}
if ($count > 1) {
throw new DbException("Expected one row, got $count: $sql", ERROR_DB_TOO_MANY_ROWS);
}
return $this->fetchOne();
}
/**
* Execute a prepared SQL statement.
*
* Execute the given SQL statement as prepared statement. It requires a parameter array which can be empty.
*
* The result of the query can be retrieved by using one of the fetch methods.
*
* Subsequent calls to this method will overwrite the result of previous calls.
*
* @param string $sql SQL statement with prepared statement variable.
* @param array $parameterArray parameter array for prepared statement execution.
* @return mixed
*/
public function execute($sql, array $parameterArray = array()) {
$this->store->setVar(SYSTEM_SQL_FINAL, $sql, STORE_SYSTEM);
$this->stmt = $this->pdo->prepare($sql);
if (false === $this->stmt->execute($parameterArray)) {
throw new DbException($this->stmt->errorInfo()[2]);
}
$count = $this->stmt->rowCount();
$this->store->setVar(SYSTEM_SQL_COUNT, $count, STORE_SYSTEM);
return $count;
}
/**
* Fetch all rows of the result as associative array.
*
* @return mixed false in case of error.
* Empty string is returned if the query didn't yield any rows.
* All rows as Multi Assoc array if $mode!=IMPLODE_ALL.
* All rows and all columns imploded to one string if $mode=IMPLODE_ALL
*
*/
public function fetchAll($mode = '') {
if ($this->stmt == null) {
return false;
}
if ($this->stmt->rowCount() === 0) {
return ($mode === ROW_IMPLODE_ALL) ? "" : array();
}
$str = "";
if ($mode === ROW_IMPLODE_ALL) {
foreach ($this->stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
$str .= implode($row);
}
return $str;
} else {
return $this->stmt->fetchAll(\PDO::FETCH_ASSOC);
}
}
/**
* Fetch one row of the result as associative array.
*
* @return null|mixed the first row of the result of Database::execute() as associative array.
* If Database::execute() has never been called prior a call to this method, false is returned.
* Empty string is returned if the query didn't yield any rows.
*/
public function fetchOne() {
if ($this->stmt == null) {
return false;
}
if ($this->stmt->rowCount() === 0) {
return array();
}
return $this->stmt->fetch(\PDO::FETCH_ASSOC);
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 1/1/16
* Time: 12:43 PM
*/
namespace qfq\exceptions;
/**
* Class UserException
*
* Thrown by Form or FormElement on User errors
*
* @package qfq\exceptions
*/
class DbException extends \Exception {
/*
* @return string HTML formatted error string
*/
public function formatMessage() {
$message = '<div style="border: 1px black solid">';
$message .= $this->getMessage();
$message .= '</div>';
return ($message);
}
}
DROP TABLE IF EXISTS person;
CREATE TABLE person (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128),
firstname VARCHAR(128),
gender VARCHAR(10)
);
# Fake data
INSERT INTO person (id, name, firstname, gender) VALUES
(NULL, 'Doe', 'John', 'male'),
(NULL, 'Smith', 'Jane', 'female');
DROP TABLE IF EXISTS address;
CREATE TABLE address (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
person_id BIGINT,
street VARCHAR(128),
city VARCHAR(128),
country ENUM('Switzerland', 'Austria', 'France', 'Germany'),
gr_id_typ BIGINT
);
DROP TABLE IF EXISTS gruppe;
CREATE TABLE gruppe (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
value VARCHAR(255),
typ VARCHAR(255),
t1 TEXT
);
<?php
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
use qfq\helper;
use qfq\store;
require_once(__DIR__ . '/../../qfq/Database.php');
require_once(__DIR__ . '/../../qfq/store/Store.php');
/**
* Class AbstractDatabaseTest
*/
abstract class AbstractDatabaseTest extends PHPUnit_Framework_TestCase {
/**
* @var null
*/
static protected $mysqli = null;
/**
* @var \PDO
*/
protected $pdo = null;
/**
* @var qfq\Database
*/
protected $database = null;
/*
* @var qfq\store\Store
*/
protected $store = null;
/**
* @param $filename
* @param $ignoreError
*
* @throws Exception
*/
protected function executeSQLFile($filename, $ignoreError) {
$sqlStatements = file_get_contents($filename);
if (!self::$mysqli->multi_query($sqlStatements) && !$ignoreError) {
throw new Exception("mysqli->multi_query() failed: " . AbstractDatabaseTest::$mysqli->error);
}
// discard all results
do {
if ($res = AbstractDatabaseTest::$mysqli->store_result()) {
$res->free();
}
} while (AbstractDatabaseTest::$mysqli->more_results() && AbstractDatabaseTest::$mysqli->next_result());
}
/**
* @throws Exception
*/
protected function setUp() {
// Init the store also reads db credential configuration
$this->store = \qfq\store\Store::getInstance('');
// SWITCH to TestDB
$this->store->setVar(SYSTEM_DB, $this->store->getVar(SYSTEM_TESTDB, STORE_SYSTEM), STORE_SYSTEM);
/// Establish additional mysqli access
$dbserver = $this->store->getVar(SYSTEM_DBSERVER, STORE_SYSTEM);
$dbuser = $this->store->getVar(SYSTEM_DBUSER, STORE_SYSTEM);
$db = $this->store->getVar(SYSTEM_DB, STORE_SYSTEM);
$dbpw = $this->store->getVar(SYSTEM_DBPW, STORE_SYSTEM);
if (self::$mysqli === null) {
self::$mysqli = new \mysqli($dbserver, $dbuser, $dbpw, $db);
if (self::$mysqli->connect_errno) {
throw new Exception("Unable to connect to mysql server");
}
}
if ($this->database === null) {
$this->database = new \qfq\Database();
$this->pdo = $this->database->pdo;
}
}
}
<?php
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
require_once(__DIR__ . '/AbstractDatabaseTest.php');
require_once(__DIR__ . '/../../qfq/Database.php');
class DatabaseTest extends AbstractDatabaseTest {
public function testExecute() {
$this->database->execute('SELECT * FROM person');
$this->assertEquals(2, $this->database->rowCount());
}
public function testFetchOneRowAssocEmptyResult() {
$this->database->execute('SELECT * FROM person WHERE id=0', []);
$this->assertEquals(array(), $this->database->fetchOne());
}
public function testFetchOne() {
$this->database->execute('SELECT * FROM person');
$oneRow = $this->database->fetchOne();
$this->assertArrayHasKey('name', $oneRow);
$this->assertArrayHasKey('firstname', $oneRow);
$this->assertArrayHasKey('gender', $oneRow);
$this->assertEquals('Doe', $oneRow['name']);
$this->assertEquals('John', $oneRow['firstname']);
}
public function testFetchAll() {
$this->database->execute('SELECT * FROM person ORDER BY id LIMIT 2');
$allRows = $this->database->fetchAll();
$this->assertCount(2, $allRows);
$this->assertEquals(1, $allRows[0]['id']);
$this->assertEquals(2, $allRows[1]['id']);
}
public function testFetchAllEmpty() {
$this->database->execute('SELECT * FROM person WHERE id=0 ORDER BY id');
$this->assertEquals(array(), $this->database->fetchAll());
}
public function testQuerySimple() {
$expected = [
[
'id' => '1',
'name' => 'Doe',
'firstname' => 'John',
'gender' => 'male'
],
[
'id' => '2',
'name' => 'Smith',
'firstname' => 'Jane',
'gender' => 'female'
],
];
// Check read rows
$dataArray = $this->database->querySimple('SELECT * FROM person ORDER BY id LIMIT 2');
// Check count
$this->assertEquals(2, $this->database->rowCount());
$this->assertEquals(2, $this->store->getVar(SYSTEM_SQL_COUNT, STORE_SYSTEM));
// Compare rows
$this->assertSame($expected, $dataArray);
// Check rows
// Same as above, but specify 'ROW_REGULAR'
$dataArray = $this->database->querySimple('SELECT * FROM person ORDER BY id LIMIT 2', ROW_REGULAR);
$this->assertEquals(2, $this->database->rowCount());
// Check rows
$this->assertSame($expected, $dataArray);
// ROW_EXACT_1
$dataArray = $this->database->querySimple('SELECT * FROM person WHERE id=1', ROW_EXACT_1);
$this->assertEquals(1, $this->database->rowCount());
// Check rows
$this->assertSame($expected[0], $dataArray);
// ROW_EMPTY_IS_OK - one record
$dataArray = $this->database->querySimple('SELECT * FROM person WHERE id=1', ROW_EMPTY_IS_OK);
$this->assertEquals(1, $this->database->rowCount());
// Check rows
$this->assertSame($expected[0], $dataArray);
// ROW_EMPTY_IS_OK - no record
$dataArray = $this->database->querySimple('SELECT * FROM person WHERE id=0', ROW_EMPTY_IS_OK);
$this->assertEquals(0, $this->database->rowCount());
// Check rows
$this->assertSame(array(), $dataArray);
// Check Implode: 2 records
$data = $this->database->querySimple('SELECT * FROM person ORDER BY id LIMIT 2', ROW_IMPLODE_ALL);
$this->assertEquals(implode($expected[0]) . implode($expected[1]), $data);
// Check Implode: 1 record
$data = $this->database->querySimple('SELECT * FROM person ORDER BY id LIMIT 1', ROW_IMPLODE_ALL);
$this->assertEquals(implode($expected[0]), $data);
// Check Implode 0 record
$data = $this->database->querySimple('SELECT * FROM person WHERE id=0 ORDER BY id LIMIT 2', ROW_IMPLODE_ALL);
$this->assertEquals('', $data);
}
/**
* @expectedException \qfq\exceptions\DbException
*
*/
public function testSanatizeException() {
$this->database->querySimple('some garbage');
}
protected function setUp() {
parent::setUp();
$this->executeSQLFile(__DIR__ . '/fixtures/Generic.sql', true);
}
//
// public function testFetchEntireColumn() {
// $this->database->execute('SELECT * FROM DatabaseClass ORDER BY a', []);
//
// $column2 = $this->database->fetchEntireColumn(1);
//
// $this->assertCount(3, $column2);
//
// $this->assertEquals('abc', $column2[0]);
// $this->assertEquals('def', $column2[1]);
// $this->assertEquals('ghi', $column2[2]);
// }
//
// public function testFetchEntireColumnEmptyResult() {
// $this->database->execute('SELECT * FROM DatabaseClass WHERE a=0 ORDER BY a', []);
//
// $this->assertNull($this->database->fetchEntireColumn(1));
// }
//
// public function testFetchOneRowAssocOnUpdate() {
// $this->database->execute('UPDATE DatabaseClass SET a = :a, b = :b WHERE a=1', [':a' => '1', ':b' => 'abc']);
// $this->assertNull($this->database->fetchOneRowAssoc());
// }
//
// public function testFetchAllRowsAssocOnUpdate() {
// $this->database->execute('UPDATE DatabaseClass SET a = :a, b = :b WHERE a=1', [':a' => '1', ':b' => 'abc']);
// $this->assertNull($this->database->fetchAllRowsAssoc());
// }
//
// public function testFetchEntireColumnOnUpdate() {
// $this->database->execute('UPDATE DatabaseClass SET a = :a, b = :b WHERE a=1', [':a' => '1', ':b' => 'abc']);
// $this->assertNull($this->database->fetchEntireColumn(100));
// }
//
// public function testParameters() {
// $this->database->execute('SELECT * FROM DatabaseClass WHERE a = :valueOfA', [':valueOfA' => 1]);
//
// $this->assertEquals(1, $this->database->rowCount());
// }
//
// public function testIsTableAttribute() {
// $this->assertTrue($this->database->isTableAttribute('DatabaseClass', 'a'));
// $this->assertFalse($this->database->isTableAttribute('DatabaseClass', 'bla'));
// }
//
// public function testIsEnumColumn() {
// $this->assertTrue($this->database->isSetEnumColumn('SetEnumTable', 'color'));
// $this->assertFalse($this->database->isSetEnumColumn('SetEnumTable', 'id'));
// }
//
// /**
// * @expectedException PDOException
// */
// public function testIsEnumColumnInvalidTable() {