Refactoring database specific functionality to Database class

This commit is contained in:
ohartl 2016-02-28 00:35:32 +01:00
parent 34f1b7d5f1
commit 7ef1e6d53a
7 changed files with 568 additions and 350 deletions

View file

@ -0,0 +1,504 @@
<?php
class Database
{
/**
* @var Database
*/
protected static $instance = null;
/**
* @var mysqli
*/
protected $db;
/**
* @var string
*/
protected $config;
/**
* @var string
*/
protected $lastQuery;
protected function __construct($host, $user, $password, $database)
{
if(!static::isInitialized()){
$this->config = $database;
$this->db = new mysqli($host, $user, $password, $database);
if($this->db->connect_errno > 0){
$this->db = null;
die('Unable to connect to database ['.$this->db->connect_error.']');
}
}
}
protected function __clone()
{
}
/**
* @return Database
*/
public static function getInstance()
{
return static::$instance;
}
/**
* @param Database $instance
*/
protected static function setInstance($instance)
{
static::$instance = $instance;
}
/**
* @param string $host
* @param string $user
* @param string $password
* @param string $database
*/
public static function init($host, $user, $password, $database)
{
if(!static::isInitialized()){
static::setInstance(
new static($host, $user, $password, $database)
);
}
}
/**
* @return bool
*/
public static function isInitialized()
{
return !is_null(static::$instance);
}
/**
*
*/
public static function mustBeInitialized()
{
if(!static::isInitialized()){
die('Database has not been initialized.');
}
}
/**
* Die with error and executed sql query
*
* @param string $errorMessage
* @param string|null $sql
*/
public function dieOnDatabaseError($errorMessage, $sql = null)
{
die('There was an error running the query ['.$errorMessage.']'.(!is_null($sql) ? ' with statement "'.$sql.'"' : ''));
}
/**
* Die if query not successful
*/
public function mustBeSuccessful()
{
if($this->db->errno !== 0){
$this->dieOnDatabaseError($this->db->error, $this->lastQuery);
}
}
/**
* Execute query
*
* @param string $query
*
* @return bool|mysqli_result
*/
public function query($query)
{
$this->lastQuery = $query;
$result = $this->db->query($query);
$this->mustBeSuccessful();
return $result;
}
/**
* @return mixed
*/
public function getInsertId()
{
return $this->db->insert_id;
}
/**
* Escape string
*
* @param string $input
*
* @return string
*/
public function escape($input)
{
return $this->db->real_escape_string($input);
}
/**
* @param string $table
* @param array $conditions
* @param string $conditionConnector
* @param null $orderBy
* @param int $limit
*
* @return bool|mysqli_result
*/
public function select($table, $conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
return $this->query(
sprintf(
"SELECT * FROM `%s` %s%s%s",
$table,
static::helperWhere($conditions, $conditionConnector),
static::helperOrderBy($orderBy),
static::helperLimit($limit)
)
);
}
/**
* Insert into table
*
* @param string $table
* @param array $values
*
* @return mixed
*/
public function insert($table, $values)
{
if(count($values) === 0){
return null;
}
$this->query(
sprintf(
"INSERT INTO `%s` (%s) VALUES %s",
$table,
static::helperAttributeList(array_keys($values)),
static::helperValueList(array_values($values))
)
);
return $this->getInsertId();
}
/**
* Update table
*
* @param string $table
* @param array $values
* @param array $conditions
* @param string $conditionConnector
*/
public function update($table, $values, $conditions = array(), $conditionConnector = 'AND')
{
if(count($values) === 0){
return;
}
$sqlValues = array();
foreach($values as $attribute => $value){
$sqlValues[] = array($attribute, '=', $value);
}
$this->query(
sprintf(
"UPDATE `%s` SET %s %s",
$table,
static::helperConditionList($sqlValues, ','),
static::helperWhere($conditions, $conditionConnector)
)
);
}
/**
* Count in table
*
* @param string $table
* @param string $byAttribute
* @param array $conditions
* @param string $conditionConnector
*
* @return int
*/
public function count($table, $byAttribute, $conditions = array(), $conditionConnector = 'AND')
{
$result = $this->query(
sprintf(
"SELECT COUNT(`%s`) FROM `%s` %s",
$byAttribute,
$table,
static::helperWhere($conditions, $conditionConnector)
)
);
return intval($result->fetch_array(MYSQLI_NUM)[0]);
}
/**
* @param string $table
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function delete($table, $attribute, $value)
{
$sql = sprintf(
"DELETE FROM `%s` %s",
$table,
static::helperWhere(array($attribute, $value))
);
$this->query($sql);
}
/**
* @param string $potentialKeyword
*
* @return bool
*/
protected static function isKeyword($potentialKeyword)
{
return in_array(
strtoupper($potentialKeyword),
array('AS', 'ASC', 'DESC')
);
}
/**
* @param array $attributes
*
* @return string
*/
public static function helperAttributeList($attributes)
{
$sqlAttributes = array();
foreach($attributes as $attribute){
if(is_string($attribute)){ // raw
$sqlAttributes[] = $attribute;
continue;
}
if(!is_array($attribute)){
$attribute = array($attribute);
}
$sqlPieces = array();
for($i = 0; $i < count($attribute); ++$i){
if(static::isKeyword($attribute[$i])){
$sqlPieces[] = sprintf("%s", $attribute[$i]);
}
elseif(isset($attribute[$i + 1]) && !static::isKeyword($attribute[$i + 1])){
$sqlPieces[] = sprintf("`%s`.`%s`", $attribute[$i], $attribute[++$i]);
}
else{
$sqlPieces[] = sprintf("`%s`", $attribute[$i]);
}
}
$sqlAttributes[] = implode(" ", $sqlPieces);
}
return sprintf(
"%s",
implode(', ', $sqlAttributes)
);
}
/**
* @param mixed $value
*
* @return string
*/
public static function helperValue($value)
{
if(is_null($value) || (is_string($value) && strtoupper($value) === 'NULL')){
return "NULL";
}
elseif(is_array($value)){
return static::helperValueList($value);
}
return sprintf(
"'%s'",
static::getInstance()->escape($value)
);
}
/**
* @param array $values
*
* @return string
*/
public static function helperValueList($values)
{
$sqlValues = array();
foreach($values as $val){
$sqlValues[] = static::helperValue($val);
}
return sprintf(
"(%s)",
implode(', ', $sqlValues)
);
}
/**
* @param array $conditions
* array('attr', '=', '3') => "`attr` = '3'"
* array(
* array('`attr` = '3') (raw SQL) => `attr` = '3'
* array('attr', 3) => `attr` = '3'
* array('attr', '=', '3') => `attr` = '3'
* array('attr', '<=', 3) => `attr` <= '3'
* array('attr', 'LIKE', '%asd') => `attr` LIKE '%asd'
* array('attr', 'IS', null) => `attr` IS NULL
* array('attr', 'IS NOT', null) => `attr` IS NOT NULL
* )
* @param string $conditionConnector AND, OR
*
* @return string
*/
public static function helperConditionList($conditions, $conditionConnector = 'AND')
{
// detect non nested array
if(count($conditions) > 0 && !is_array($conditions[0])){
$conditions = array($conditions);
}
$conditionConnector = strtoupper($conditionConnector);
if(in_array($conditionConnector, array('AND', 'OR'))){
$conditionConnector = " ".$conditionConnector;
}
$values = array();
foreach($conditions as $val){
switch(count($val)){
case 1:
// raw
$values[] = $val;
break;
case 2:
$v = static::helperValue($val[1]);
$values[] = sprintf("`%s` = %s", $val[0], $v);
break;
case 3:
$v = static::helperValue($val[2]);
$values[] = sprintf("`%s` %s %s", $val[0], strtoupper($val[1]), $v);
break;
}
}
return implode($conditionConnector." ", $values);
}
/**
* @param array $conditions
* @param string $conditionConnector AND, OR
*
* @return string
*/
public static function helperWhere($conditions, $conditionConnector = 'AND')
{
if(count($conditions) > 0){
return sprintf(
" WHERE %s",
static::helperConditionList($conditions, $conditionConnector)
);
}
return "";
}
/**
* @param array|null $orderBy Examples below:
* null => ""
* array() => ""
* array('attr1' => 'asc', 'attr2' => 'desc') => " ORDER BY `attr1` ASC, `attr2` DESC"
* array('attr1') => " ORDER BY `attr1` ASC"
*
* @return string
*/
public static function helperOrderBy($orderBy = null)
{
if(is_null($orderBy) || count($orderBy) === 0){
return "";
}
$values = array();
foreach($orderBy as $key => $val){
if(is_int($key)){
$values[] = array($val);
}
else{
$values[] = array($key, strtoupper($val));
}
}
return sprintf(
" ORDER BY %s",
static::helperAttributeList($values)
);
}
/**
* @param int|array $limit
* 0 => ""
* 3 => " LIMIT 3"
* array(3, 4) => " LIMIT 3,4"
*
* @return string
*/
public static function helperLimit($limit = 0)
{
if(is_array($limit) && count($limit) == 2){
$limit = $limit[0].",".$limit[1];
}
if(is_string($limit) || (is_int($limit) && $limit > 0)){
return sprintf(
" LIMIT %s",
$limit
);
}
return "";
}
}

View file

@ -40,10 +40,7 @@ else{
/**
* Establish database connection
*/
$db = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
if($db->connect_errno > 0){
die('Unable to connect to database [' . $db->connect_error . ']');
}
Database::init(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
/**

View file

@ -1,14 +1,5 @@
<?php
/**
* @param string $errorMessage
* @param null|string $sql
*/
function dbError($errorMessage, $sql = null)
{
die('There was an error running the query ['.$errorMessage.']'.(!is_null($sql)?' with statement "'.$sql.'"':''));
}
/**
* Add message to logfile

View file

@ -202,7 +202,7 @@ abstract class AbstractModel
{
$rows = array();
while($row = $result->fetch_assoc()) {
while($row = $result->fetch_assoc()){
$rows[] = $row;
}
@ -258,229 +258,6 @@ abstract class AbstractModel
}
/**
* @param array $attributes
*
* @return string
*/
protected static function sqlHelperAttributeList($attributes)
{
$sql = "%s";
$values = array();
$keywords = array('AS', 'ASC', 'DESC');
foreach($attributes as $val){
if(!is_array($val)){
// raw
$values[] = $val;
continue;
}
switch(count($val)){
case 1:
$values[] = "`{$val[0]}`";
break;
case 2:
if(in_array(strtoupper($val[1]), $keywords)){
$values[] = "`{$val[0]}` {$val[1]}";
}
else{
$values[] = "`{$val[0]}`.`{$val[1]}`";
}
break;
case 3:
if(in_array(strtoupper($val[1]), $keywords)){
$values[] = "`{$val[0]}` {$val[1]} `{$val[2]}`";
}
elseif(in_array(strtoupper($val[2]), $keywords)){
$values[] = "`{$val[0]}`.`{$val[1]}` {$val[2]}";
}
break;
case 4:
if(in_array(strtoupper($val[1]), $keywords)){
$values[] = "`{$val[0]}` {$val[1]} `{$val[2]}`.`{$val[3]}`";
}
elseif(in_array(strtoupper($val[2]), $keywords)){
$values[] = "`{$val[0]}`.`{$val[1]}` {$val[2]} `{$val[3]}`";
}
else{
$values[] = "`{$val[0]}`.`{$val[1]}` `{$val[2]}`.`{$val[3]}`";
}
break;
case 5:
if(in_array(strtoupper($val[2]), $keywords)){
$values[] = "`{$val[0]}`.`{$val[1]}` {$val[2]} `{$val[3]}`.`{$val[4]}`";
}
break;
}
}
return sprintf($sql, implode(', ', $values));
}
/**
* @param mixed $value
*
* @return string
*/
protected static function sqlHelperValue($value)
{
global $db;
if(is_null($value) || (is_string($value) && strtoupper($value) === 'NULL')){
return "NULL";
}
elseif(is_array($value)){
return static::sqlHelperValueList($value);
}
return "'{$db->escape_string($value)}'";
}
/**
* @param array $values
*
* @return string
*/
protected static function sqlHelperValueList($values)
{
$sql = "(%s)";
$sqlValues = array();
foreach($values as $val){
$sqlValues[] = static::sqlHelperValue($val);
}
return sprintf($sql, implode(', ', $sqlValues));
}
/**
* @param array $conditions
* array('attr', '=', '3') => "`attr` = '3'"
* array(
* array('`attr` = '3') (raw SQL) => `attr` = '3'
* array('attr', 3) => `attr` = '3'
* array('attr', '=', '3') => `attr` = '3'
* array('attr', '<=', 3) => `attr` <= '3'
* array('attr', 'LIKE', '%asd') => `attr` LIKE '%asd'
* array('attr', 'IS', null) => `attr` IS NULL
* array('attr', 'IS NOT', null) => `attr` IS NOT NULL
* )
* @param string $conditionConnector AND, OR
*
* @return string
*/
protected static function sqlHelperConditionList($conditions, $conditionConnector = 'AND')
{
$values = array();
// detect non nested array
if(count($conditions) > 0 && !is_array($conditions[0])){
$conditions = array($conditions);
}
$conditionConnector = strtoupper($conditionConnector);
if(in_array($conditionConnector, array('AND', 'OR'))){
$conditionConnector = " ".$conditionConnector;
}
$sql = "`%s` %s %s";
foreach($conditions as $val){
switch(count($val)){
case 1:
// raw
$values[] = $val;
break;
case 2:
$v = static::sqlHelperValue($val[1]);
$values[] = sprintf($sql, $val[0], "=", $v);
break;
case 3:
$v = static::sqlHelperValue($val[2]);
$values[] = sprintf($sql, $val[0], strtoupper($val[1]), $v);
break;
}
}
return implode($conditionConnector." ", $values);
}
/**
* @param array $conditions
* @param string $conditionConnector AND, OR
*
* @return string
*/
protected static function sqlHelperWhere($conditions, $conditionConnector = 'AND')
{
if(count($conditions) > 0){
$sql = " WHERE %s";
return sprintf($sql, static::sqlHelperConditionList($conditions, $conditionConnector));
}
return "";
}
/**
* @param array|null $orderBy Examples below:
* null => ""
* array() => ""
* array('attr1' => 'asc', 'attr2' => 'desc') => " ORDER BY `attr1` ASC, `attr2` DESC "
* array('attr1') => " ORDER BY `attr1` ASC "
*
* @return string
*/
protected static function sqlHelperOrderBy($orderBy = null)
{
if(!is_null($orderBy) && count($orderBy) > 0){
$sql = " ORDER BY %s";
$values = array();
foreach($orderBy as $key => $val){
if(is_int($key)){
$values[] = array($val);
}
else{
$values[] = array($key, strtoupper($val));
}
}
return sprintf($sql, static::sqlHelperAttributeList($values));
}
return "";
}
/**
* @param int|array $limit
* 0 => ""
* 3 => " LIMIT 3 "
* array(3, 4) => " LIMIT 3,4 "
*
* @return string
*/
protected static function sqlHelperLimit($limit = 0)
{
$sql = " LIMIT %s";
if(is_string($limit) || (is_int($limit) && $limit > 0)){
return sprintf($sql, $limit);
}
elseif(is_array($limit) && count($limit) == 2){
return sprintf($sql, $limit[0].",".$limit[1]);
}
return "";
}
/**
* Find all models by raw sql
*
@ -491,11 +268,7 @@ abstract class AbstractModel
*/
public static function findAllRaw($sql, $useSpecificModel = null)
{
global $db;
if(!$result = $db->query($sql)){
dbError($db->error, $sql);
}
$result = Database::getInstance()->query($sql);
if(is_null($useSpecificModel)){
return static::createMultipleFromDbResult($result);
@ -518,11 +291,7 @@ abstract class AbstractModel
*/
public static function findRaw($sql, $useSpecificModel = null)
{
global $db;
if(!$result = $db->query($sql)){
dbError($db->error, $sql);
}
$result = Database::getInstance()->query($sql);
if(is_null($useSpecificModel)){
return static::createFromDbResult($result);
@ -538,49 +307,43 @@ abstract class AbstractModel
/**
* Find all models
*
* @param array|null $orderBy see sqlHelperOrderBy
* @param array|null $orderBy see helperOrderBy
*
* @return ModelCollection|static[]
*/
public static function findAll($orderBy = null)
{
$sql = "SELECT * FROM `".static::$table."`"
.static::sqlHelperOrderBy($orderBy);
return static::findAllRaw($sql);
return static::findWhere(array(), 'AND', $orderBy);
}
/**
* Find models by a condition
*
* @param array $conditions see sqlHelperConditionArray
* @param string $conditionConnector see sqlHelperConditionArray
* @param array $conditions see helperConditionArray
* @param string $conditionConnector see helperConditionArray
* @param array|null $orderBy
* @param int $limit see sqlHelperLimit
* @param int $limit see helperLimit
*
* @return ModelCollection|static[]|AbstractModel|null
*/
public static function findWhere($conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
$sql = "SELECT * FROM `".static::$table."`"
.static::sqlHelperWhere($conditions, $conditionConnector)
.static::sqlHelperOrderBy($orderBy)
.static::sqlHelperLimit($limit);
$result = Database::getInstance()->select(static::$table, $conditions, $conditionConnector, $orderBy, $limit);
if($limit === 1){
return static::findRaw($sql);
return static::createFromDbResult($result);
}
return static::findAllRaw($sql);
return static::createMultipleFromDbResult($result);
}
/**
* Find first model matching a condition
*
* @param array $conditions see sqlHelperConditionArray
* @param string $conditionConnector see sqlHelperConditionArray
* @param array $conditions see helperConditionArray
* @param string $conditionConnector see helperConditionArray
* @param array|null $orderBy
*
* @return AbstractModel|null
@ -606,66 +369,28 @@ abstract class AbstractModel
/**
* Save model data to database
*
* @return bool
*/
public function save()
{
global $db;
$data = $this->preSave($this->data);
if(is_null($this->getId())){
// insert
$attributes = array();
$values = array();
foreach($this->attributeDbAttributeMapping as $attribute => $sqlAttribute){
if($sqlAttribute === static::$idAttribute){
continue;
}
$attributes[] = array($sqlAttribute);
$values[] = $data[$attribute];
$values = array();
foreach($this->attributeDbAttributeMapping as $attribute => $sqlAttribute){
if($sqlAttribute === static::$idAttribute){
continue;
}
$sql = "INSERT INTO `".static::$table."`"
." (".static::sqlHelperAttributeList($attributes).")"
." VALUES ".static::sqlHelperValueList($values);
$values[$sqlAttribute] = $data[$attribute];
}
if(is_null($this->getId())){
$insertId = Database::getInstance()->insert(static::$table, $values);
$this->setId(intval($insertId));
}
else{
// update
$values = array();
foreach($this->attributeDbAttributeMapping as $attribute => $sqlAttribute){
if($sqlAttribute === static::$idAttribute){
continue;
}
$values[] = array($sqlAttribute, '=', $data[$attribute]);
}
$sql = "UPDATE `".static::$table."`"
." SET ".static::sqlHelperConditionList($values, ',')
.static::sqlHelperWhere(array(static::$idAttribute, $this->getId()));
Database::getInstance()->update(static::$table, $values, array(static::$idAttribute, $this->getId()));
}
if($stmt = $db->prepare($sql)){
if($stmt->execute()){
if(is_null($this->getId())){
$this->setId(intval($db->insert_id));
}
return true;
}
else{
dbError($db->error, $sql);
}
}
return false;
}
/**
@ -675,23 +400,39 @@ abstract class AbstractModel
*/
public function delete()
{
global $db;
if(!is_null($this->getId())){
$sql = "DELETE FROM `".static::$table."`"
.static::sqlHelperWhere(array(static::$idAttribute, $this->getId()));
if($stmt = $db->prepare($sql)){
if($stmt->execute()){
return true;
}
else{
dbError($db->error, $sql);
}
}
Database::getInstance()->delete(static::$table, static::$idAttribute, $this->getId());
return true;
}
return false;
}
/**
* Count models by a condition
*
* @param array $conditions see helperConditionArray
* @param string $conditionConnector see helperConditionArray
*
* @return int
*/
public static function countWhere($conditions = array(), $conditionConnector = 'AND')
{
return Database::getInstance()->count(static::$table, static::$idAttribute, $conditions, $conditionConnector);
}
/**
* Count all models
*
* @return int
*/
public static function count()
{
return static::countWhere();
}
}

View file

@ -194,7 +194,7 @@ abstract class AbstractRedirect extends AbstractModel
$domains = array();
foreach($sources as $source){
$emailParts = explode('@', $source);
if(count($emailParts) === 2) {
if(count($emailParts) === 2){
$domains[] = $emailParts[1];
}
}
@ -296,7 +296,7 @@ UNION
public static function findMultiAll($orderBy = array(DBC_ALIASES_SOURCE))
{
$sql = static::generateRedirectBaseQuery()
.static::sqlHelperOrderBy($orderBy);
.Database::helperOrderBy($orderBy);
return static::findAllRaw($sql);
}
@ -305,9 +305,9 @@ UNION
public static function findMultiWhere($conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
$sql = static::generateRedirectBaseQuery()
.static::sqlHelperWhere($conditions, $conditionConnector)
.static::sqlHelperOrderBy($orderBy)
.static::sqlHelperLimit($limit);
.Database::helperWhere($conditions, $conditionConnector)
.Database::helperOrderBy($orderBy)
.Database::helperLimit($limit);
if($limit === 1){
return static::findRaw($sql);

View file

@ -65,13 +65,7 @@ class Domain extends AbstractModel
*/
public function countUsers()
{
global $db;
if(!$result = $db->query("SELECT COUNT(`".DBC_USERS_ID."`) FROM `".DBT_USERS."` WHERE `".DBC_USERS_DOMAIN."` = '{$this->getDomain()}'")){
dbError($db->error);
}
return $result->fetch_array(MYSQLI_NUM)[0];
return User::countWhere(array(DBC_USERS_DOMAIN, $this->getDomain()));
}
@ -80,13 +74,7 @@ class Domain extends AbstractModel
*/
public function countRedirects()
{
global $db;
if(!$result = $db->query("SELECT COUNT(`".DBC_ALIASES_ID."`) FROM `".DBT_ALIASES."` WHERE `".DBC_ALIASES_SOURCE."` LIKE '%@{$this->getDomain()}%'")){
dbError($db->error);
}
return $result->fetch_array(MYSQLI_NUM)[0];
return AbstractRedirect::countWhere(array(DBC_ALIASES_SOURCE, 'LIKE', "%@{$this->getDomain()}%"));
}
}

View file

@ -157,14 +157,11 @@ class User extends AbstractModel
*/
public static function getMailboxLimitDefault()
{
global $db;
if(defined('DBC_USERS_MAILBOXLIMIT')){
$sql = "SELECT DEFAULT(".DBC_USERS_MAILBOXLIMIT.") FROM `".static::$table."` LIMIT 1";
if(!$result = $db->query($sql)){
dbError($db->error, $sql);
}
$result = Database::getInstance()->query($sql);
if($result->num_rows === 1){
$row = $result->fetch_array();
@ -227,7 +224,7 @@ class User extends AbstractModel
global $adminDomainLimits;
if($this->isDomainLimited()){
if (!is_array($adminDomainLimits[$this->getEmail()])) {
if(!is_array($adminDomainLimits[$this->getEmail()])){
throw new InvalidArgumentException('Config value of admin domain limits for email "'.$this->getEmail().'" needs to be of type array.');
}