Updating Database service class to PDO mysql instead of mysql_* functions

This commit is contained in:
Pepijn Over 2014-02-11 16:15:12 +01:00
parent fa9d6daab1
commit 00b15237e6
5 changed files with 356 additions and 236 deletions

View File

@ -1,3 +1,11 @@
#########################
#
# Version 2.x
# ?, 2014
#
#########################
- Switched from mysql_* to PDO.
######################### #########################
# #
# Version 2.1.0 # Version 2.1.0

View File

@ -52,8 +52,8 @@ You can also clone the git repo at <http://github.com/phpservermon/phpservermon>
## REQUIREMENTS ## REQUIREMENTS
* PHP 5.3+ * PHP 5.3+
* PHP packages: cURL, MySQL * PHP cURL package
* MySQL Database * PHP PDO mysql driver
## INSTALL ## INSTALL

View File

@ -80,7 +80,7 @@ foreach($includes as $file) {
// init db connection // init db connection
$db = new psm\Service\Database(); $db = new psm\Service\Database();
if($db->getLink() !== null) { if($db->status()) {
psm_load_conf(); psm_load_conf();
} else { } else {

12
src/psm/Module/Install.class.php Normal file → Executable file
View File

@ -103,9 +103,9 @@ class Install extends AbstractModule {
} else { } else {
$this->addResult('cURL installed'); $this->addResult('cURL installed');
} }
if(!function_exists('mysql_connect')) { if(!in_array('mysql', \PDO::getAvailableDrivers())) {
$errors++; $errors++;
$this->addResult('php-mysql needs to be installed.', 'error'); $this->addResult('The PDO MySQL driver needs to be installed.', 'error');
} }
if($errors > 0) { if($errors > 0) {
@ -170,7 +170,7 @@ class Install extends AbstractModule {
$config['name'] $config['name']
); );
if(is_resource($this->db->getLink())) { if($this->db->status()) {
$this->addResult('Connection to MySQL successful.'); $this->addResult('Connection to MySQL successful.');
$config_php = $this->writeConfigFile($config); $config_php = $this->writeConfigFile($config);
if($config_php === true) { if($config_php === true) {
@ -222,7 +222,7 @@ class Install extends AbstractModule {
$this->addResult('No valid configuration found.', 'error'); $this->addResult('No valid configuration found.', 'error');
return $this->executeConfig(); return $this->executeConfig();
} }
if(!is_resource($this->db->getLink())) { if(!$this->db->status()) {
$this->addResult('MySQL connection failed.', 'error'); $this->addResult('MySQL connection failed.', 'error');
return; return;
} }
@ -234,7 +234,7 @@ class Install extends AbstractModule {
if(!empty($if_table_exists)) { if(!empty($if_table_exists)) {
$this->addResult('Table ' . $name . ' already exists in your database!'); $this->addResult('Table ' . $name . ' already exists in your database!');
} else { } else {
$this->db->query($sql); $this->db->exec($sql);
$this->addResult('Table ' . $name . ' added.'); $this->addResult('Table ' . $name . ' added.');
} }
} }
@ -256,7 +256,7 @@ class Install extends AbstractModule {
$install_queries = $queries->upgrade(PSM_VERSION, $version_from); $install_queries = $queries->upgrade(PSM_VERSION, $version_from);
foreach($install_queries as $sql) { foreach($install_queries as $sql) {
$this->db->query($sql); $this->db->exec($sql);
} }
$this->addResult('Installation finished!'); $this->addResult('Installation finished!');

View File

@ -29,116 +29,130 @@ namespace psm\Service;
class Database { class Database {
protected $debug = array(); /**
protected $last_inserted_id; * DB hostname
protected $link; * @var string $db_host
protected $num_rows_found; */
protected $num_rows_returned; protected $db_host;
function __construct($host = null, $user = null, $pass = null, $db = null) {
if($host != null && $user != null && $pass != null && $db != null) {
$this->connect($host, $user, $pass, $db);
} elseif(defined('PSM_DB_HOST') && defined('PSM_DB_USER') && defined('PSM_DB_PASS') && defined('PSM_DB_NAME')) {
$this->connect(PSM_DB_HOST, PSM_DB_USER, PSM_DB_PASS, PSM_DB_NAME);
}
}
/** /**
* Connect to the database * DB name
* @var string $db_name
*/
protected $db_name;
/**
* DB user password
* @var string $db_pass
*/
protected $db_pass;
/**
* DB username
* @var string $db_user
*/
protected $db_user;
/**
* PDOStatement of last query
* @var \PDOStatement $last
*/
protected $last;
/**
* Mysql db connection identifer
* @var \PDO $pdo
* @see pdo()
*/
protected $pdo;
/**
* Connect status
* @var boolean
* @see connect()
*/
protected $status = false;
/**
* Constructor
*
* @param string $host * @param string $host
* @param string $db
* @param string $user * @param string $user
* @param string $pass * @param string $pass
* @param string $db
* @return boolean
*/ */
protected function connect($host, $user, $pass, $db) { function __construct($host = null, $user = null, $pass = null, $db = null) {
$this->link = mysql_connect($host, $user, $pass); if($host != null && $user != null && $pass != null && $db != null) {
$this->db_host = $host;
if (!mysql_select_db($db, $this->link)) { $this->db_name = $db;
trigger_error(mysql_errno() . ": " . mysql_error()); $this->db_user = $user;
return false; $this->db_pass = $pass;
$this->connect();
} elseif(defined('PSM_DB_HOST') && defined('PSM_DB_USER') && defined('PSM_DB_PASS') && defined('PSM_DB_NAME')) {
$this->db_host = PSM_DB_HOST;
$this->db_name = PSM_DB_NAME;
$this->db_user = PSM_DB_USER;
$this->db_pass = PSM_DB_PASS;
$this->connect();
} }
mysql_query("SET NAMES utf8;", $this->link);
mysql_query("SET CHARACTER SET 'utf8';", $this->link);
return true;
} }
/** /**
* Executes a query * Exectues query and fetches result.
* *
* @param $sql string MySQL query * If you dont want to fetch a result, use exec().
* @return resource mysql resource * @param string $query SQL query
* @param boolean $fetch automatically fetch results, or return PDOStatement?
* @return array|\PDOStatement if $fetch = true, array, otherwise \PDOStatement
*/ */
public function query($query, $fetch = true) {
public function executeQuery($sql) {
$result = mysql_query($sql, $this->getLink());
if (mysql_error($this->getLink())) {
trigger_error(mysql_errno($this->getLink()) . ': ' . mysql_error($this->getLink()));
return false;
}
if (is_resource($result) && mysql_num_rows($result) > 0) {
// Rows returned
$this->num_rows_returned = mysql_num_rows($result);
// Rows found
$result_num_rows_found = $this->fetchResults(mysql_query('SELECT FOUND_ROWS();'));
$this->num_rows_found = $result_num_rows_found[0]['FOUND_ROWS()'];
}
if (substr(strtolower(trim($sql)), 0, 6) == 'insert') {
// we have an insert
$this->last_inserted_id = mysql_insert_id($this->getLink());
$result = $this->last_inserted_id;
}
return $result;
}
/**
* Exectues query and fetches result
*
* @param $query string MySQL query
* @return $result array
*/
public function query($query) {
// Execute query and process results // Execute query and process results
$result_resource = $this->executeQuery($query); try {
$result = $this->fetchResults($result_resource); $this->last = $this->pdo()->query($query);
} catch (\PDOException $e) {
$this->error($e);
}
if($fetch && $this->last != false) {
$cmd = strtolower(substr($query, 0, 6));
switch($cmd) {
case 'insert':
// insert query, return insert id
$result = $this->getLastInsertedId();
break;
case 'update':
case 'delete':
// update/delete, returns rowCount
$result = $this->getNumRows();
break;
default:
$result = $this->last->fetchAll(\PDO::FETCH_ASSOC);
break;
}
} else {
$result = $this->last;
}
return $result; return $result;
} }
/** /**
* Fetch results from a query * Execute SQL statement and return number of affected rows
* * @param string $query
* @param resource $result result from a mysql query * @return int
* @return array $array with results (multi-dimimensial) for more than one rows
*/ */
public function exec($query) {
public function fetchResults($result_query){ try {
$this->last = $this->pdo()->exec($query);
if (!is_resource($result_query)) { } catch (\PDOException $e) {
return array(); $this->error($e);
} }
$num_rows = mysql_num_rows($result_query); return $this->last;
$result = array();
while($record = mysql_fetch_assoc($result_query)) {
$result[] = $record;
}
return $result;
} }
/** /**
* Performs a select on the given table and returns an multi dimensional associative array with results * Performs a select on the given table and returns an multi dimensional associative array with results
*
* @param string $table tablename * @param string $table tablename
* @param mixed $where string or array with where data * @param mixed $where string or array with where data
* @param array $fields array with fields to be retrieved. if empty all fields will be retrieved * @param array $fields array with fields to be retrieved. if empty all fields will be retrieved
@ -147,7 +161,6 @@ class Database {
* @param string $direction ASC or DESC. Defaults to ASC * @param string $direction ASC or DESC. Defaults to ASC
* @return array multi dimensional array with results * @return array multi dimensional array with results
*/ */
public function select($table, $where = null, $fields = null, $limit = '', $orderby = null, $direction = 'ASC'){ public function select($table, $where = null, $fields = null, $limit = '', $orderby = null, $direction = 'ASC'){
// build query // build query
$query_parts = array(); $query_parts = array();
@ -164,16 +177,11 @@ class Database {
$query_parts[] = "FROM `{$table}`"; $query_parts[] = "FROM `{$table}`";
// Where clause // Where clause
$query_parts[] = $this->buildWhereClause($table, $where); $query_parts[] = $this->buildSQLClauseWhere($table, $where);
// Order by // Order by
if ($orderby !== null && !empty($orderby)) { if($orderby) {
$orderby_clause = 'ORDER BY '; $query_parts[] = $this->buildSQLClauseOrderBy($orderby, $direction);
foreach($orderby as $field) {
$orderby_clause .= "`{$field}`, ";
}
$query_parts[] = substr($orderby_clause, 0, -2) . ' ' . $direction;
} }
// Limit // Limit
@ -183,203 +191,307 @@ class Database {
$query = implode(' ', $query_parts); $query = implode(' ', $query_parts);
// Get results return $this->query($query);
$result = $this->query($query);
return $result;
} }
public function selectRow($table, $where = null, $fields = null, $limit = '', $orderby = null, $direction = 'ASC') { /**
$result = $this->select($table, $where, $fields, $limit, $orderby, $direction); * Alias to select() but uses limit = 1 to return only one row.
* @param string $table tablename
* @param mixed $where string or array with where data
* @param array $fields array with fields to be retrieved. if empty all fields will be retrieved
* @param array $orderby fields for the orderby clause
* @param string $direction ASC or DESC. Defaults to ASC
* @return array
*/
public function selectRow($table, $where = null, $fields = null, $orderby = null, $direction = 'ASC') {
$result = $this->select($table, $where, $fields, '1', $orderby, $direction);
if ($this->getNumRowsReturned() == '1') { if(isset($result[0])) {
$result = $result[0]; $result = $result[0];
} }
return $result; return $result;
} }
/** /**
* Remove a record from database * Remove a record from database
*
* @param string $table tablename * @param string $table tablename
* @param mixed $where Where clause array or primary Id (string) or where clause (string) * @param mixed $where Where clause array or primary Id (string) or where clause (string)
* @return boolean * @return int number of affected rows
*/ */
public function delete($table, $where = null){ public function delete($table, $where = null){
$sql = 'DELETE FROM `'.$table.'` ' . $this->buildSQLClauseWhere($table, $where);
if ($table != '') { return $this->exec($sql);
$sql = 'DELETE FROM `'.$table.'` ' . $this->buildWhereClause($table, $where);
$this->query($sql);
}
} }
/** /**
* Insert or update data to the database * Insert or update data to the database
* * @param string $table table name
* @param array $table table name
* @param array $data data to save or insert * @param array $data data to save or insert
* @param mixed $where either string ('user_id=2' or just '2' (works only with primary field)) or array with where clause (only when updating) * @param mixed $where either string ('user_id=2' or just '2' (works only with primary field)) or array with where clause (only when updating)
*/ */
public function save($table, $data, $where = null) { public function save($table, array $data, $where = null) {
if ($where === null) { if ($where === null) {
// insert mode // insert mode
$query = "INSERT INTO "; $query = "INSERT INTO ";
$exec = false;
} else { } else {
$query = "UPDATE "; $query = "UPDATE ";
$exec = true;
} }
$query .= "`{$table}` SET "; $query .= "`{$table}` SET ";
foreach($data as $field => $value) { foreach($data as $field => $value) {
$value = $this->escapeValue($value); if(is_null($value)) {
$query .= "`{$table}`.`{$field}`='{$value}', "; $value = 'NULL';
} else {
$value = $this->quote($value);
}
$query .= "`{$table}`.`{$field}`={$value}, ";
} }
$query = substr($query, 0, -2) . ' ' . $this->buildWhereClause($table, $where); $query = substr($query, 0, -2) . ' ' . $this->buildSQLClauseWhere($table, $where);
return $this->query($query); if($exec) {
return $this->exec($query);
} else {
return $this->query($query);
}
}
/**
* Insert multiple rows into a single table
*
* This method is preferred over calling the insert() lots of times
* so it can be optimized to be inserted with 1 query.
* It can only be used if all inserts have the same fields, records
* that do not match the fields provided in the first record will be
* skipped.
*
* @param type $table
* @param array $data
* @return \PDOStatement
* @see insert()
*/
public function insertMultiple($table, array $data) {
if(empty($data)) return false;
// prepare first part
$query = "INSERT INTO `{$table}` ";
$fields = array_keys($data[0]);
$query .= "(`".implode('`,`', $fields)."`) VALUES ";
// prepare all rows to be inserted with placeholders for vars (\?)
$q_part = array_fill(0, count($fields), '?');
$q_part = "(".implode(',', $q_part).")";
$q_part = array_fill(0, count($data), $q_part);
$query .= implode(',', $q_part);
$pst = $this->pdo()->prepare($query);
$i = 1;
foreach($data as $row) {
// make sure the fields of this row are identical to first row
$diff_keys = array_diff_key($fields, array_keys($row));
if(!empty($diff_keys)) {
continue;
}
foreach($fields as $field) {
$pst->bindParam($i++, $row[$field]);
}
}
try {
$this->last = $pst->execute();
} catch (\PDOException $e) {
$this->error($e);
}
return $this->last;
}
/**
* Quote a string
* @param string $value
* @return string
*/
public function quote($value) {
return $this->pdo()->quote($value);
}
/**
* Get the PDO object
* @return \PDO
*/
public function pdo() {
return $this->pdo;
}
/**
* Get number of rows of last statement
* @return int number of rows
*/
public function getNumRows() {
return $this->last->rowCount();
}
/**
* Get the last inserted id after an insert
* @return int
*/
public function getLastInsertedId() {
return $this->pdo()->lastInsertId();
} }
/** /**
* Build WHERE clause for query * Build WHERE clause for query
*
* @param string $table table name * @param string $table table name
* @param mixed $where can be primary id (eg '2'), can be string (eg 'name=pepe') or can be array * @param mixed $where can be primary id (eg '2'), can be string (eg 'name=pepe') or can be array
* @return string sql where clause * @return string sql where clause
* @see buildSQLClauseOrderBy()
*/ */
public function buildWhereClause($table, $where = null) { public function buildSQLClauseWhere($table, $where = null) {
$query = ''; $query = '';
if ($where !== null) { if ($where !== null) {
if (is_array($where)) { if (is_array($where)) {
$query .= " WHERE "; $query .= " WHERE ";
foreach($where as $field => $value) { foreach($where as $field => $value) {
$value = $this->escapeValue($value); $query .= "`{$table}`.`{$field}`={$this->quote($value)} AND ";
$query .= "`{$table}`.`{$field}`='{$value}' AND "; }
} $query = substr($query, 0, -5);
$query = substr($query, 0, -5); } else {
} else { if (strpos($where, '=') === false) {
if (strpos($where, '=') === false) { // no field given, use primary field
// no field given, use primary field $primary = $this->getPrimary($table);
$structure = $this->getTableStructure($table); $query .= " WHERE `{$table}`.`{$primary}`={$this->quote($where)}";
$where = $this->escapeValue($where); } elseif (strpos(strtolower(trim($where)), 'where') === false) {
$query .= " WHERE `{$table}`.`{$structure['primary']}`='{$where}'"; $query .= " WHERE {$where}";
} elseif (strpos(strtolower(trim($where)), 'where') === false) { } else {
$query .= " WHERE {$where}"; $query .= ' '.$where;
} else { }
$query .= ' '.$where; }
} }
}
}
return $query; return $query;
} }
/** /**
* Get table structure and primary key * Build ORDER BY clause for a query
* * @param mixed $order_by can be string (with or without order by) or array
* @param string $table table name * @param string $direction
* @return array primary key and database structure * @return string sql order by clause
*/ * @see buildSQLClauseWhere()
public function getTableStructure($table) { */
if ($table == '') return false; public function buildSQLClauseOrderBy($order_by, $direction) {
$query = '';
$structure = $this->query("DESCRIBE `{$table}`"); if ($order_by !== null) {
if (is_array($order_by)) {
$query .= " ORDER BY ";
if (empty($structure)) return false; foreach($order_by as $field) {
$query .= "`{$field}`, ";
// use arrray search function to get primary key }
$search_needle = array( // remove trailing ", "
'key' => 'Key', $query = substr($query, 0, -2);
'value' => 'PRI' } else {
); if (strpos(strtolower(trim($order_by)), 'order by') === false) {
$primary = pep_array_search_key_value( $query .= " ORDER BY {$order_by}";
$structure, } else {
array( $query .= ' '.$order_by;
'key' => 'Key', }
'value' => 'PRI' }
) }
); if(strlen($query) > 0) {
// check if "ASC" or "DESC" is already in the order by clause
$primary_field = $structure[$primary[0]['path'][0]]['Field']; if(strpos(strtolower(trim($query)), 'asc') === false && strpos(strtolower(trim($query)), 'desc') === false) {
return array( $query .= ' '.$direction;
'primary' => $primary_field, }
'fields' => $structure
);
}
/**
* Get information about a field from the database
*
* @param string $table
* @param string $field
* @return array mysql field information
*/
public function getFieldInfo($table, $field) {
if ($table == '' || $field == '') return array();
$db_structure = $this->getTableStructure($table);
$field_info = pep_array_search_key_value(
$db_structure,
array(
'key' => 'Field',
'value' => $field
)
);
if (empty($field_info)) {
return array();
} }
// return field info return $query;
return $field_info[0]['result'];
} }
/** /**
* Formats the value for the SQL query to secure against injections * Get the host of the current connection
*
* @param string $value
* @return string * @return string
*/ */
public function escapeValue($value) { public function getDbHost() {
if(get_magic_quotes_gpc()) { return $this->db_host;
$value = stripslashes($value);
}
$value = mysql_real_escape_string($value, $this->link);
return $value;
} }
/** /**
* Get number of rows found * Get the db name of the current connection
* * @return string
* @return int number of rows found */
*/ public function getDbName() {
public function getNumRowsFound() { return $this->db_name;
return $this->num_rows_found; }
}
/**
* Get number of rows returned
*
* @return int number of rows returned
*/
public function getNumRowsReturned() {
return $this->num_rows_returned;
}
/** /**
* Get the database connection identifier * Get the db user of the current connection
* * @return string
* @return object db connection */
*/ public function getDbUser() {
public function getLink() { return $this->db_user;
return $this->link; }
/**
* Get status of the connection
* @return boolean
*/
public function status() {
return $this->status;
}
/**
* Connect to the database.
*
* @return resource mysql resource
*/
protected function connect() {
// Initizale connection
try {
$this->pdo = new \PDO(
'mysql:host='.$this->db_host.';dbname='.$this->db_name.';charset=utf8',
$this->db_user,
$this->db_pass
);
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->status = true;
} catch (\PDOException $e) {
$this->status = false;
return $this->onConnectFailure($e);
}
return $this->pdo;
}
/**
* Is called after connection failure
*/
protected function onConnectFailure(\PDOException $e) {
trigger_error('MySQL connection failed: '.$e->getMessage(), E_USER_WARNING);
return false;
}
/**
* Disconnect from current link
*/
protected function disconnect() {
$this->pdo = null;
}
/**
* Handle a PDOException
* @param \PDOException $e
*/
protected function error(\PDOException $e) {
trigger_error('SQL error: ' . $e->getMessage(), E_USER_WARNING);
} }
} }