c
This commit is contained in:
503
inc/Hura8/Database/ConnectDB.php
Normal file
503
inc/Hura8/Database/ConnectDB.php
Normal file
@@ -0,0 +1,503 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\Database;
|
||||
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
final class ConnectDB implements iConnectDB
|
||||
{
|
||||
use ClassCacheTrait;
|
||||
|
||||
private $debug = false;
|
||||
|
||||
private static $instance = [];
|
||||
private static $cnn_props = [];
|
||||
|
||||
/* @var $connection \mysqli */
|
||||
private $connection;
|
||||
|
||||
private static $traces = [];
|
||||
|
||||
private $db_id = '';
|
||||
|
||||
private function __construct($db_id = 'main', $debug = false)
|
||||
{
|
||||
// enable database debug
|
||||
if($debug) $this->debug = $debug; // (defined('ENABLE_DB_DEBUG') && ENABLE_DB_DEBUG);
|
||||
if($db_id) $this->db_id = $db_id;
|
||||
|
||||
if(!sizeof(self::$cnn_props)) {
|
||||
self::$cnn_props = self::setConnectionSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private static function setConnectionSettings() {
|
||||
return static::getCache("getConnectionSettings", function (){
|
||||
$db_file = CONFIG_DIR.'/db.php';
|
||||
if(file_exists($db_file)) {
|
||||
return include $db_file;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @return array
|
||||
*/
|
||||
public function getTableDefaultItemInfo($table) {
|
||||
$column_info = $this->getTableInfo($table);
|
||||
$default_info = [];
|
||||
foreach ($column_info as $field => $info) {
|
||||
$default_info[$field] = $info['COLUMN_DEFAULT'];
|
||||
}
|
||||
|
||||
return $default_info;
|
||||
}
|
||||
|
||||
|
||||
//28-07-2015 get all columns of a table
|
||||
public function getTableInfo($table) {
|
||||
|
||||
return self::getCache('getTableInfo-'.$table, function () use ($table) {
|
||||
|
||||
$db_props = $this->getConnectionProps();
|
||||
if(!$db_props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = $db_props['db'] ?? null;
|
||||
if(!$database) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = $this->runQuery(
|
||||
"SELECT
|
||||
`COLUMN_NAME` ,
|
||||
COLUMN_DEFAULT,
|
||||
ORDINAL_POSITION,
|
||||
COLUMN_DEFAULT,
|
||||
DATA_TYPE,
|
||||
CHARACTER_MAXIMUM_LENGTH,
|
||||
COLUMN_TYPE,
|
||||
COLUMN_KEY,
|
||||
EXTRA
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE `TABLE_SCHEMA` = ? AND`TABLE_NAME` = ? ",
|
||||
['s', 's'], [$database, $table]
|
||||
);
|
||||
|
||||
$output = [];
|
||||
foreach( $this->fetchAll($query) as $row){
|
||||
$output[$row['COLUMN_NAME']] = $row;
|
||||
}
|
||||
|
||||
return $output;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected function setTrace($query, $start_time, $msg = ''){
|
||||
static::$traces[] = [
|
||||
"msg" => $msg,
|
||||
"query" => $query,
|
||||
"start_time" => $start_time,
|
||||
"total_time" => round($this->getCurrentTime() - $start_time, 5),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public static function getTraces() {
|
||||
$query_count = sizeof(static::$traces);
|
||||
$query_time = array_sum(array_map(function ($item){ return $item['total_time'];}, static::$traces)) ;
|
||||
return [
|
||||
'query_count' => $query_count,
|
||||
'query_time' => $query_time,
|
||||
'list' => static::$traces,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
private function __clone(){
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $db_id
|
||||
* @param bool $debug
|
||||
* @return iConnectDB
|
||||
*/
|
||||
public static function getInstance(string $db_id = '', bool $debug = false) {
|
||||
if( ! $db_id ) $db_id = 'main';
|
||||
|
||||
if(!isset(ConnectDB::$instance[$db_id])) {
|
||||
ConnectDB::$instance[$db_id] = new self($db_id, $debug);
|
||||
}
|
||||
|
||||
return ConnectDB::$instance[$db_id];
|
||||
}
|
||||
|
||||
|
||||
//close all connections
|
||||
public static function close() {
|
||||
foreach (ConnectDB::$instance as $db_id => $cnn) {
|
||||
$cnn->disconnect();
|
||||
}
|
||||
// reset
|
||||
ConnectDB::$instance = [];
|
||||
}
|
||||
|
||||
|
||||
public function isConnected(): bool {
|
||||
return ($this->connection instanceof \mysqli);
|
||||
}
|
||||
|
||||
|
||||
private function getConnectionProps() {
|
||||
return self::$cnn_props[$this->db_id] ?? null;
|
||||
}
|
||||
|
||||
|
||||
private function connect()
|
||||
{
|
||||
// already connect
|
||||
if($this->connection instanceof \mysqli) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// connection props not set
|
||||
$db_props = $this->getConnectionProps();
|
||||
if(!$db_props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = $db_props['host'] ?? null;
|
||||
$user = $db_props['user'] ?? null;
|
||||
$pass = $db_props['pass'] ?? null;
|
||||
$database = $db_props['db'] ?? null;
|
||||
$db_charset = $db_props['charset'] ?? 'latin1';
|
||||
|
||||
if(!$host || !$user || !$pass || !$database) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
//create the object
|
||||
$cnn = \mysqli_init();
|
||||
$cnn->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5);
|
||||
//specify the read timeout
|
||||
if (!defined('MYSQLI_OPT_READ_TIMEOUT')) {
|
||||
define ('MYSQLI_OPT_READ_TIMEOUT', 11);
|
||||
}
|
||||
$cnn->options(MYSQLI_OPT_READ_TIMEOUT, 10);
|
||||
|
||||
if($cnn->real_connect($host, $user, $pass)){
|
||||
$cnn->select_db($database);
|
||||
$cnn->query("SET NAMES ".$db_charset); // UTF8|latin1
|
||||
$cnn->query("SET sql_mode=''"); //for old version to work with mysql 5.7
|
||||
//mysqli_select_db($this->connection, $database);
|
||||
//mysqli_query($this->connection, "SET NAMES UTF8") ;//set utf8 if database using utf-8 encode
|
||||
//mysqli_query($this->connection, "SET NAMES latin1") ;//set back to latin1
|
||||
//mysqli_query($this->connection, "SET sql_mode=''") ;//for old version to work with mysql 5.7
|
||||
$this->connection = $cnn;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//throw new \Exception('Unable to connect');
|
||||
return false;
|
||||
|
||||
}catch (\Exception $e) {
|
||||
//die($e->getMessage());
|
||||
echo $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function disconnect()
|
||||
{
|
||||
if($this->connection instanceof \mysqli) {
|
||||
$this->connection->close();
|
||||
$this->connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function ping() {
|
||||
if($this->connection) {
|
||||
$this->connection->ping();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param array $bind_types
|
||||
* @param array $bind_values
|
||||
* @param bool $get_affected_row_or_id get the affected row or newly insert-id from the query, default return the mysqli_result
|
||||
* @return \mysqli_result | false | int
|
||||
*/
|
||||
public function runQuery($query, array $bind_types=[], array $bind_values=[], $get_affected_row_or_id = false)
|
||||
{
|
||||
$start_time = $this->getCurrentTime();
|
||||
|
||||
// connect on demand
|
||||
if(!$this->connect()) {
|
||||
if($this->debug) $this->setTrace($query, $start_time, 'runQuery: Connection fails');
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $this->connection->prepare( $query );
|
||||
if(!$stmt) {
|
||||
//throw new \Exception($stmt->error);
|
||||
//throw new \Exception($this->connection->error);
|
||||
if($this->debug) {
|
||||
$this->setTrace($query, $start_time, 'runQuery: '.$this->connection->error);
|
||||
die("runQuery error: ".$this->connection->error.". Query: ".$query);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(sizeof($bind_types) && sizeof($bind_types) == sizeof($bind_values)) {
|
||||
if(!$stmt->bind_param(join('', $bind_types), ...$bind_values)) {
|
||||
if($this->debug) {
|
||||
$this->setTrace($query, $start_time, 'runQuery: bind_param '.$this->connection->error);
|
||||
die('runQuery: bind_param '.$this->connection->error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!$stmt->execute()) {
|
||||
if($this->debug) {
|
||||
$this->setTrace($query, $start_time, 'runQuery: execute '.$this->connection->error);
|
||||
die("runQuery error: ".$this->connection->error.". Query: ".$query);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->debug) $this->setTrace($query, $start_time, '');
|
||||
|
||||
if($get_affected_row_or_id) {
|
||||
return $stmt->insert_id ?: $stmt->affected_rows;
|
||||
}
|
||||
|
||||
// default
|
||||
return $stmt->get_result();
|
||||
}
|
||||
|
||||
|
||||
private function getCurrentTime(){
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Unsafe, make sure all variables are properly escaped
|
||||
*/
|
||||
public function multi_query(array $array_query){
|
||||
|
||||
if(!sizeof($array_query)) return false;
|
||||
|
||||
$start_time = $this->getCurrentTime();
|
||||
|
||||
// late connect
|
||||
if(!$this->connect()) {
|
||||
if($this->debug) $this->setTrace(join("; ", $array_query), $start_time, 'multi_query: Connection fails');
|
||||
return false;
|
||||
}
|
||||
|
||||
$multi_query = join(";", $array_query);
|
||||
$multi_query = str_replace("\n", " ", $multi_query);
|
||||
//remove double ; if exist
|
||||
$multi_query = preg_replace("/;(\s+)?;/i", ";", $multi_query);
|
||||
|
||||
$set = 0;
|
||||
$list = [];
|
||||
if(mysqli_multi_query($this->connection, $multi_query)){
|
||||
// flush multi_queries, so any query after can run
|
||||
do {
|
||||
$set ++;
|
||||
/* store first result set */
|
||||
if ($result = mysqli_store_result($this->connection)) {
|
||||
/*while ($row = mysqli_fetch_assoc($result)) {
|
||||
$list[$set][] = $row;
|
||||
}*/
|
||||
$list[$set] = $this->fetchAll($result);
|
||||
mysqli_free_result($result);
|
||||
}
|
||||
} while (mysqli_more_results($this->connection) && mysqli_next_result($this->connection));
|
||||
}
|
||||
|
||||
if($this->debug) $this->setTrace(join(";", $array_query), $start_time);
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
// a simple utitily method which we use very frequently
|
||||
public function getItemInfo($table_name, $id_value, $id_field = 'id'){
|
||||
return $this->select(
|
||||
$table_name,
|
||||
[],
|
||||
[
|
||||
$id_field => ["=", $id_value],
|
||||
],
|
||||
'',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//$table_name = 'table_abc'
|
||||
/*$fields = array(
|
||||
"field_name_1",
|
||||
);*/
|
||||
/*$where_condition = array(
|
||||
"field_name_1" => ["=", "value_1"],
|
||||
"field_name_2" => ["LIKE", "value_1"],
|
||||
);*/
|
||||
public function select($table_name, $fields =[], $where_condition=[], $order_by = '', $limit='1'){
|
||||
|
||||
// connect on demand
|
||||
if(!$this->connect()) {
|
||||
if($this->debug) $this->setTrace($table_name, 0, 'select: Connection fails');
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_time = $this->getCurrentTime();
|
||||
|
||||
$check_fields = [];
|
||||
if(sizeof($fields) && $fields[0] != '*') $check_fields = $fields;
|
||||
$check_fields = array_merge($check_fields, array_keys($where_condition));
|
||||
|
||||
$cols_in_tables = $this->filterColumnInTable($table_name, $check_fields);
|
||||
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
$condition_list = [];
|
||||
$permitted_operator = [">", ">=", "<", "<=", "!=", "=", "BETWEEN", "LIKE", "IS"]; // https://dev.mysql.com/doc/refman/8.0/en/non-typed-operators.html
|
||||
foreach ($where_condition as $field_name => $operator_value) {
|
||||
|
||||
// if any field invalid, stop the query
|
||||
if(!in_array($field_name, $cols_in_tables)) {
|
||||
echo "Invalid field_name ".$field_name;
|
||||
return false;
|
||||
};
|
||||
|
||||
// invalidate
|
||||
if(!is_array($operator_value) || sizeof($operator_value) != 2) continue;
|
||||
|
||||
$operator = $operator_value[0];
|
||||
if(!in_array($operator, $permitted_operator)) continue;
|
||||
|
||||
$value = $operator_value[1];
|
||||
|
||||
$condition_list[] = " `".$field_name."` ".$operator." ? ";
|
||||
$bind_types[] = (is_int($value)) ? 'd' : 's';
|
||||
$bind_values[] = $value;
|
||||
}
|
||||
|
||||
$select_fields = (sizeof($fields) > 0) ? array_map(function ($field){ return "`".$field."`";}, $fields) : ["*"];
|
||||
$order_by_query = ($order_by) ? "ORDER BY ".preg_replace("/[^a-z0-9\s_]/i", "", $order_by) : "";
|
||||
$safe_limit = ($limit) ? preg_replace("/[^0-9\,\s]/i", "", $limit) : "1";
|
||||
$where_condition = (sizeof($condition_list)) ? " AND " . join(" AND ", $condition_list) : "";
|
||||
|
||||
$query = "SELECT ".join(',', $select_fields)."
|
||||
FROM `".$table_name."` WHERE 1 ".$where_condition."
|
||||
".$order_by_query."
|
||||
LIMIT ".$safe_limit;
|
||||
|
||||
$stmt = $this->connection->prepare( $query );
|
||||
if(!$stmt) return false;
|
||||
|
||||
if(sizeof($bind_types)) {
|
||||
$stmt->bind_param(join('', $bind_types), ...$bind_values);
|
||||
}
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
if($this->debug) $this->setTrace($query, $start_time);
|
||||
|
||||
return ($safe_limit == '1') ? $result->fetch_assoc() : $result->fetch_all();
|
||||
}
|
||||
|
||||
|
||||
protected function filterColumnInTable($table_name, array $list_check_fields) {
|
||||
$table_columns = $this->getTableInfo($table_name);
|
||||
|
||||
$safe_fields = [];
|
||||
foreach ($list_check_fields as $field_name) {
|
||||
// make sure the field exists
|
||||
if(!array_key_exists($field_name, $table_columns)) continue;
|
||||
|
||||
$safe_fields[] = $field_name;
|
||||
}
|
||||
|
||||
return $safe_fields;
|
||||
}
|
||||
|
||||
|
||||
//fetch array
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function fetchArray(\mysqli_result $resource){
|
||||
return $resource->fetch_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \mysqli_result | false $resource
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetchAssoc( $resource){
|
||||
if(!$resource) {
|
||||
//die("Resource failed: code fetchAssoc 122");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $resource->fetch_assoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \mysqli_result | false $resource
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function fetchAll($resource){
|
||||
|
||||
if(!$resource) return [];
|
||||
|
||||
//some hosting does not enable this function, so this is the work-around
|
||||
if( ! function_exists("mysqli_fetch_all")) {
|
||||
$item_list = array();
|
||||
while ($item = $this->fetchAssoc($resource)) {
|
||||
$item_list[] = $item;
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
return ($resource) ? $resource->fetch_all(MYSQLI_ASSOC) : [];
|
||||
}
|
||||
|
||||
//affected rows from last query
|
||||
public function get_affected_rows(){
|
||||
return $this->connection->affected_rows;
|
||||
}
|
||||
|
||||
|
||||
public function getErrorNo(){
|
||||
return mysqli_errno($this->connection);
|
||||
}
|
||||
|
||||
|
||||
public function __destruct() {
|
||||
$this->disconnect();
|
||||
}
|
||||
}
|
||||
16
inc/Hura8/Database/MysqlValue.php
Normal file
16
inc/Hura8/Database/MysqlValue.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\Database;
|
||||
|
||||
class MysqlValue
|
||||
{
|
||||
protected $value;
|
||||
|
||||
public function __construct($value = null) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
55
inc/Hura8/Database/iConnectDB.php
Normal file
55
inc/Hura8/Database/iConnectDB.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\Database;
|
||||
|
||||
interface iConnectDB
|
||||
{
|
||||
|
||||
public function isConnected(): bool;
|
||||
|
||||
public function ping() ;
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param array $bind_types
|
||||
* @param array $bind_values
|
||||
* @param bool $get_affected_row_or_id get the affected row or newly insert-id from the query, default return the mysqli_result
|
||||
* @return \mysqli_result | false | int
|
||||
*/
|
||||
public function runQuery($query, array $bind_types=[], array $bind_values=[], $get_affected_row_or_id = false);
|
||||
|
||||
/**
|
||||
* @deprecated Unsafe, make sure all variables are properly escaped
|
||||
*/
|
||||
public function multi_query(array $array_query);
|
||||
|
||||
|
||||
// a simple utitily method which we use very frequently
|
||||
public function getItemInfo($table_name, $id_value, $id_field = 'id');
|
||||
|
||||
//$table_name = 'table_abc'
|
||||
/*$fields = array(
|
||||
"field_name_1",
|
||||
);*/
|
||||
/*$where_condition = array(
|
||||
"field_name_1" => ["=", "value_1"],
|
||||
"field_name_2" => ["LIKE", "value_1"],
|
||||
);*/
|
||||
public function select($table_name, $fields =[], $where_condition=[], $order_by = '', $limit='1');
|
||||
|
||||
|
||||
/**
|
||||
* @param \mysqli_result | false $resource
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetchAssoc( $resource);
|
||||
|
||||
/**
|
||||
* @param \mysqli_result | false $resource
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function fetchAll($resource);
|
||||
|
||||
public function getErrorNo();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user