. * * @package phpservermon * @author Pepijn Over * @copyright Copyright (c) 2008-2017 Pepijn Over * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 * @version Release: @package_version@ * @link http://www.phpservermonitor.org/ **/ namespace psm\Service; class Database { /** * DB hostname * @var string $db_host */ protected $db_host; /** * DB port * @var string|integer $db_port */ protected $db_port; /** * 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 int|bool|\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 $user * @param string $pass * @param string $db * @param string|integer $port */ public function __construct($host = null, $user = null, $pass = null, $db = null, $port = '') { if ($host != null && $user != null && $pass !== null && $db != null) { $this->db_host = $host; $this->db_port = $port; $this->db_name = $db; $this->db_user = $user; $this->db_pass = $pass; $this->connect(); } } /** * Exectues query and fetches result. * * If you dont want to fetch a result, use exec(). * @param string $query SQL query * @param boolean $fetch automatically fetch results, or return PDOStatement? * @return \PDOStatement|int|bool|array object */ public function query($query, $fetch = true) { // Execute query and process results try { $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; } /** * Execute SQL statement and return number of affected rows * @param string $query * @return int */ public function exec($query) { try { $this->last = $this->pdo()->exec($query); } catch (\PDOException $e) { $this->error($e); } return $this->last; } /** * Prepare and execute SQL statement with parameters * @param string $query SQL statement * @param array $parameters An array of values with as many elements as there are * bound parameters in the SQL statement * @param boolean $fetch automatically fetch results, or return PDOStatement? * @return array|\PDOStatement if $fetch = true, array, otherwise \PDOStatement */ public function execute($query, $parameters, $fetch = true) { try { $this->last = $this->pdo()->prepare($query); $this->last->execute($parameters); } catch (\PDOException $e) { $this->error($e); } if ($fetch && $this->last != false) { $result = $this->last->fetchAll(\PDO::FETCH_ASSOC); } else { $result = $this->last; } return $result; } /** * Performs a select on the given table and returns an multi dimensional associative array with results * @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 string $limit limit. for example: 0,30 * @param array $orderby fields for the orderby clause * @param string $direction ASC or DESC. Defaults to ASC * @return \PDOStatement array multi dimensional array with results */ public function select($table, $where = null, $fields = null, $limit = '', $orderby = null, $direction = 'ASC') { // build query $query_parts = array(); $query_parts[] = 'SELECT SQL_CALC_FOUND_ROWS'; // Fields if ($fields !== null && !empty($fields)) { $query_parts[] = "`" . implode('`,`', $fields) . "`"; } else { $query_parts[] = ' * '; } // From $query_parts[] = "FROM `{$table}`"; // Where clause $query_parts[] = $this->buildSQLClauseWhere($table, $where); // Order by if ($orderby) { $query_parts[] = $this->buildSQLClauseOrderBy($orderby, $direction); } // Limit if ($limit != '') { $query_parts[] = 'LIMIT ' . $limit; } $query = implode(' ', $query_parts); return $this->query($query); } /** * 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 (isset($result[0])) { $result = $result[0]; } return $result; } /** * Remove a record from database * @param string $table tablename * @param mixed $where Where clause array or primary Id (string) or where clause (string) * @return int number of affected rows */ public function delete($table, $where = null) { $sql = 'DELETE FROM `' . $table . '` ' . $this->buildSQLClauseWhere($table, $where); return $this->exec($sql); } /** * Insert or update data to the database * @param string $table table name * @param array $data data to save or insert * @param string|array $where either string ('user_id=2' or just '2' (works only with primary field)) or * array with where clause (only when updating) * @return int|array|\PDOStatement */ public function save($table, array $data, $where = null) { if ($where === null) { // insert mode $query = "INSERT INTO "; $exec = false; } else { $query = "UPDATE "; $exec = true; } $query .= "`{$table}` SET "; foreach ($data as $field => $value) { if (is_null($value)) { $value = 'NULL'; } else { $value = $this->quote($value); } $query .= "`{$table}`.`{$field}`={$value}, "; } $query = substr($query, 0, -2) . ' ' . $this->buildSQLClauseWhere($table, $where); if ($exec) { return $this->exec($query); } 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 string $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; } /** * Check if a certain table exists. * @param string $table * @return boolean */ public function ifTableExists($table) { $table = $this->quote($table); $db = $this->quote($this->getDbName()); $if_exists = "SELECT COUNT(*) AS `cnt` FROM `information_schema`.`tables` WHERE `table_schema` = {$db} AND `table_name` = {$table}; "; $if_exists = $this->query($if_exists); if (isset($if_exists[0]['cnt']) && $if_exists[0]['cnt'] == 1) { return true; } else { false; } } /** * 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 * @param string $table table name * @param mixed $where can be primary id (eg '2'), can be string (eg 'name=pepe') or can be array * @return string sql where clause * @see buildSQLClauseOrderBy() */ public function buildSQLClauseWhere($table, $where = null) { $query = ''; if ($where !== null) { if (is_array($where)) { $query .= " WHERE "; foreach ($where as $field => $value) { $query .= "`{$table}`.`{$field}`={$this->quote($value)} AND "; } $query = substr($query, 0, -5); } else { if (strpos($where, '=') === false) { // no field given, use primary field $primary = $this->getPrimary($table); $query .= " WHERE `{$table}`.`{$primary}`={$this->quote($where)}"; } elseif (strpos(strtolower(trim($where)), 'where') === false) { $query .= " WHERE {$where}"; } else { $query .= ' ' . $where; } } } return $query; } /** * Build ORDER BY clause for a query * @param mixed $order_by can be string (with or without order by) or array * @param string $direction * @return string sql order by clause * @see buildSQLClauseWhere() */ public function buildSQLClauseOrderBy($order_by, $direction) { $query = ''; if ($order_by !== null) { if (is_array($order_by)) { $query .= " ORDER BY "; foreach ($order_by as $field) { $query .= "`{$field}`, "; } // remove trailing ", " $query = substr($query, 0, -2); } else { if (strpos(strtolower(trim($order_by)), 'order by') === false) { $query .= " ORDER BY {$order_by}"; } else { $query .= ' ' . $order_by; } } } if (strlen($query) > 0) { // check if "ASC" or "DESC" is already in the order by clause if ( strpos(strtolower(trim($query)), 'asc') === false && strpos(strtolower(trim($query)), 'desc') === false ) { $query .= ' ' . $direction; } } return $query; } /** * Get the host of the current connection * @return string */ public function getDbHost() { return $this->db_host; } /** * Get the db name of the current connection * @return string */ public function getDbName() { return $this->db_name; } /** * Get the db user of the current connection * @return string */ public function getDbUser() { return $this->db_user; } /** * Get status of the connection * @return boolean */ public function status() { return $this->status; } /** * Connect to the database. * * @return resource mysql resource */ protected function connect() { $isHostUnixSocket = strpos($this->db_host, ':') === 0; // Initizale connection try { if ($isHostUnixSocket) { $this->pdo = new \PDO( 'mysql:unix_socket=' . ltrim($this->db_host, ':') . ';dbname=' . $this->db_name . ';charset=utf8', $this->db_user, $this->db_pass ); } else { $this->pdo = new \PDO( 'mysql:host=' . $this->db_host . ';port=' . $this->db_port . ';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); } }