ringfinger/backend/classes/core/MySqlDatabase.php

276 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
final class MySqlDatabase implements DatabaseInterface
{
private const CHARS_ALLOWED_IN_TABLE_NAMES = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-';
private ?PDO $connection;
private ?PDOStatement $cursor;
private bool $isTransactionOpen = false;
public function __construct(
string $hostname = Setting::MYSQL_HOST,
string $user = Setting::MYSQL_USER,
string $password = Setting::MYSQL_PASSWORD,
string $database = Setting::MYSQL_DATABASE
) {
try {
$this->connection = new PDO("mysql:host=$hostname;dbname=$database", $user, $password);
} catch (Throwable $e) {
throw new DatabaseException('Couldn\'t connect to the database!');
}
}
public function __destruct()
{
$this->connection = null;
}
public function Query(string $query, array $params = []): void
{
$this->cursor = $this->connection->prepare($query);
if (!$this->cursor) {
throw new DatabaseException(
'Initialization of database cursor failed',
DatabaseException::CONNECTION_FAILED
);
}
foreach ($params as $key => $param) {
if (is_bool($param)) {
$param = (int)$param;
}
$this->cursor->bindValue(':' . $key, $param);
}
if (!$this->cursor->execute()) {
throw new DatabaseException($this->cursor->errorInfo()[2], $this->cursor->errorInfo()[1]);
}
}
public function getResult(): array
{
$result = [];
while ($fetch = $this->cursor->fetchObject()) {
$row = [];
foreach (get_object_vars($fetch) as $key => $value) {
$row[$key] = $value;
}
$result[] = $row;
}
return $result;
}
/**
* Selects backend from a table.
*/
public function Select(
string $tableName,
array $fields = [],
array $conditions = [],
int $limit = 0,
array $orderBy = [],
bool $asc = true,
int $offset = 0
): array {
if (!self::isValidTableName($tableName)) {
[];
}
if (count($fields) === 0) {
$fieldsExpression = '*';
} else {
$fieldsExpression = implode(',', $fields);
}
$conditionsExpression = '';
$conditionPairs = [];
foreach ($conditions as $field => $value) {
$conditionPairs[] = sprintf('%s = :%s ', $field, $field);
}
if (count($conditions) > 0) {
$conditionsExpression = 'WHERE ';
$conditionsExpression .= implode(' AND ', $conditionPairs);
}
$orderStatement = '';
if (count($orderBy) > 0) {
$orderStatement = 'ORDER BY ' . implode(',', $orderBy);
if (!$asc) {
$orderStatement .= ' DESC';
}
}
$limitStatement = '';
if ($limit > 0) {
$limitStatement = 'LIMIT ' . $limit;
}
$offsetStatement = '';
if ($offset > 0) {
$offsetStatement = 'OFFSET ' . $offset;
}
$query = sprintf(
'SELECT %s FROM %s %s %s %s %s',
$fieldsExpression,
$tableName,
$conditionsExpression,
$orderStatement,
$limitStatement,
$offsetStatement
);
try {
$this->Query($query, $conditions);
} catch (Throwable $e) {
return [];
}
return $this->getResult();
}
/**
* Deletes rows from a table.
*/
public function Delete(string $table, array $conditions): void
{
if (count($conditions) === 0) {
$conditionsStatement = '1';
} else {
$conditionPairs = [];
foreach ($conditions as $field => $value) {
$conditionPairs[] = sprintf('%s=:Condition%s', $field, $field);
$conditions['Condition' . $field] = $value;
unset($conditions[$field]);
}
$conditionsStatement = implode(' AND ', $conditionPairs);
}
$query = sprintf('DELETE FROM %s WHERE %s', $table, $conditionsStatement);
$this->Query($query, $conditions);
}
public function Insert(string $table, array $fields): ?int
{
if (count($fields) === 0) {
throw new DatabaseException('Row to insert is empty!');
}
$fieldNames = implode(',', array_keys($fields));
$fieldPlaceholder = [];
foreach ($fields as $name => $value) {
$fieldPlaceholder[] = ':' . $name;
}
$query = sprintf(
'INSERT INTO %s (%s) VALUES (%s)', $table, $fieldNames, implode(',', $fieldPlaceholder)
);
$this->Query($query, $fields);
$lastInsertedId = $this->GetLastInsertedId();
if ((int)$lastInsertedId === 0) {
return null;
}
return $lastInsertedId;
}
public function Update(string $table, array $fields, array $conditions): void
{
$conditionPairs = [];
foreach ($conditions as $field => $value) {
$conditionPairs[] = sprintf('%s=:Condition%s', $field, $field);
$conditions['Condition' . $field] = $value;
unset($conditions[$field]);
}
$conditionsStatement = implode(' AND ', $conditionPairs);
$fieldPairs = [];
foreach ($fields as $field => $value) {
$fieldPairs[] = sprintf('%s=:%s', $field, $field);
}
$fieldsStatement = implode(',', $fieldPairs);
$query = sprintf('UPDATE %s SET %s WHERE %s', $table, $fieldsStatement, $conditionsStatement);
$this->Query($query, array_merge($fields, $conditions));
}
public function Count(string $table, array $conditions = []): int
{
$result = $this->Select($table, ['count(*)'], $conditions);
return (int)$result[0]['count(*)'];
}
public function hasTransaction(): bool
{
return $this->isTransactionOpen;
}
public function startTransaction(): void
{
$this->connection->beginTransaction();
$this->isTransactionOpen = true;
}
public function rollback(): void
{
$this->connection->rollBack();
$this->isTransactionOpen = false;
}
public function commit(): void
{
$this->connection->commit();
$this->isTransactionOpen = false;
}
public function GetLastInsertedId(): int
{
$this->Query('SELECT LAST_INSERT_ID() as ID');
return (int)$this->getResult()[0]['ID'];
}
/**
* Does a check if the given table name contains forbidden chars.
*/
private static function isValidTableName(string $tableName): bool
{
foreach (str_split($tableName) as $char) {
if (substr_count(self::CHARS_ALLOWED_IN_TABLE_NAMES, $char) === 0) {
return false;
}
}
return true;
}
}