phpservermon/src/psm/Service/Database.php

594 lines
16 KiB
PHP

<?php
/**
* PHP Server Monitor
* Monitor your servers and websites.
*
* This file is part of PHP Server Monitor.
* PHP Server Monitor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PHP Server Monitor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PHP Server Monitor. If not, see <http://www.gnu.org/licenses/>.
*
* @package phpservermon
* @author Pepijn Over <pep@mailbox.org>
* @copyright Copyright (c) 2008-2017 Pepijn Over <pep@mailbox.org>
* @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);
}
}