ringfinger/backend/classes/core/Table.php
2020-08-21 22:46:19 +02:00

293 lines
7.0 KiB
PHP

<?php
/**
* Basic object to load a mysql table inside an object.
*/
abstract class Table
{
public const TYPE_STRING = 1;
public const TYPE_INTEGER = 2;
public const TYPE_FLOAT = 3;
public const TYPE_DATETIME = 4;
public const TYPE_BOOL = 5;
protected const VALUE = 'value';
protected const TYPE = 'type';
protected ?DatabaseInterface $database;
protected string $tableName;
protected array $fields;
protected string $primaryKey;
protected bool $isPrimKeyManual = false;
public function __construct(string $tableName, $id, ?DatabaseInterface & $database)
{
$this->tableName = $tableName;
$this->fields = [];
$this->database = $database;
$this->database->Query(sprintf('DESCRIBE %s', $tableName));
$result = $this->database->getResult();
foreach ($result as $field) {
$sqlType = substr_count(
$field['Type'], '(') === 0 ? $field['Type'] : strstr($field['Type'],
'(',
true
);
switch ($sqlType) {
case 'varchar':
case 'char':
case 'text':
case 'longtext':
case 'mediumtext':
case 'tinytext':
$type = self::TYPE_STRING;
break;
case 'int':
case 'smallint':
case 'mediumint':
case 'bigint':
$type = self::TYPE_INTEGER;
break;
case 'float':
case 'decimal':
case 'double':
case 'real':
$type = self::TYPE_FLOAT;
break;
case 'datetime':
case 'date':
$type = self::TYPE_DATETIME;
break;
case 'tinyint':
$type = self::TYPE_BOOL;
break;
default:
throw new DatabaseException(
sprintf('Type %s of field %s couldn\'t be handled', $sqlType, $field['Field']),
ServerStatus::INTERNAL_ERROR
);
}
$this->addField($field['Field'], $type);
if ($field['Key'] === 'PRI') {
$this->primaryKey = $field['Field'];
}
}
if (!$this->isPrimKeyManual && $id !== null) {
$this->loadById($id);
}
}
public function getPrimaryKey()
{
if ($this->primaryKey === null) {
return null;
}
return $this->getField($this->primaryKey);
}
protected function addField(string $name, int $type): void
{
if (!self::IsValidType($type)) {
throw new DatabaseException(
sprintf('Field %s has invalid type of %s!', $name, $type),
ServerStatus::INTERNAL_ERROR
);
}
$this->fields[$name] = [self::VALUE => null, self::TYPE => $type];
}
protected function loadById($id): void
{
$this->database->Query(
sprintf('SELECT * FROM %s WHERE %s = :id', $this->tableName, $this->primaryKey),
['id' => $id]
);
$result = $this->database->getResult();
if (count($result) === 0) {
throw new DatabaseException(
sprintf('No %s with id %d found!', $this->tableName, $id),
ServerStatus::BAD_REQUEST
);
}
foreach ($result[0] as $field => $value) {
$this->setField($field, $value);
}
}
public function Flush(): void
{
$this->database->Delete($this->tableName, []);
}
public function Delete(): void
{
$this->database->Delete($this->tableName, [$this->primaryKey => $this->getPrimaryKey()]);
foreach ($this->GetAllFieldNames() as $field) {
$this->fields[$field][self::VALUE] = null;
}
}
protected function getField(string $name)
{
if (!array_key_exists($name, $this->fields)) {
return null;
}
return $this->fields[$name][self::VALUE];
}
/**
* Sets the value for the given field inside the database.
*/
protected function setField(string $name, $value): void
{
if (!$this->HasField($name)) {
throw new DatabaseException(
sprintf('Field %s doesn\'t exist!', $name),
ServerStatus::INTERNAL_ERROR
);
}
if ($value === null) {
$this->fields[$name][self::VALUE] = null;
return;
}
switch ($this->fields[$name][self::TYPE]) {
case self::TYPE_STRING:
$this->fields[$name][self::VALUE] = (string)$value;
return;
case self::TYPE_INTEGER:
$this->fields[$name][self::VALUE] = (int)$value;
return;
case self::TYPE_FLOAT:
$this->fields[$name][self::VALUE] = (float)$value;
return;
case self::TYPE_DATETIME:
try {
$this->fields[$name][self::VALUE] = new DateTime((string)$value);
} catch (Throwable $e) {
throw new DatabaseException(
$e->getMessage(),
ServerStatus::INTERNAL_ERROR
);
}
return;
case self::TYPE_BOOL:
$this->fields[$name][self::VALUE] = (bool)$value;
}
}
/**
* Checks if the table has the given column.
*/
public function HasField(string $name): bool
{
return array_key_exists($name, $this->fields);
}
/**
* Saves the whole object into the database.
*/
public function Save(): void
{
$fields = [];
foreach ($this->GetAllFieldNames() as $fieldName) {
$field = $this->getField($fieldName);
if ($field instanceof DateTime) {
$fields[$fieldName] = $field->format('Y-m-d H:i:s');
} else if (is_bool($field)) {
$fields[$fieldName] = (int)$field;
} else {
$fields[$fieldName] = $field;
}
}
if ($this->isPrimKeyManual) {
$this->saveWithManualId($fields);
} else {
$this->saveWithPrimaryKey($fields);
}
}
/**
* @return string[]
*/
public function GetAllFieldNames(): array
{
$fieldNames = [];
foreach ($this->fields as $name => $field) {
$fieldNames[] = $name;
}
return $fieldNames;
}
/**
* Checks if the index is a valid backend type.
*/
public static function IsValidType(int $type): bool
{
$validTypes = [
self::TYPE_STRING,
self::TYPE_INTEGER,
self::TYPE_FLOAT,
self::TYPE_DATETIME,
self::TYPE_BOOL,
];
return in_array($type, $validTypes);
}
protected function saveWithManualId(array $fields): void
{
if ($this->getField($this->primaryKey) === null) {
throw new DatabaseException(
'Manual primary key must not be null!',
ServerStatus::INTERNAL_ERROR
);
}
$hasKey = (bool)$this->database->Count(
$this->tableName,
[$this->primaryKey => $this->getField($this->primaryKey)]
);
if ($hasKey) {
$this->database->Update(
$this->tableName, $fields, [$this->primaryKey => $this->getField($this->primaryKey)]
);
} else {
$this->database->Insert($this->tableName, $fields);
}
}
protected function saveWithPrimaryKey(array $fields): void
{
if ($this->getField($this->primaryKey) !== null) {
$this->database->Update(
$this->tableName, $fields, [$this->primaryKey => $this->getField($this->primaryKey)]
);
} else {
$this->setField($this->primaryKey, $this->database->Insert($this->tableName, $fields));
}
}
}