DB Entities, SQL Update
This commit is contained in:
parent
6bbf517196
commit
6d600d4004
@ -95,8 +95,6 @@ namespace Api\Contact {
|
|||||||
if (!$insertDB) {
|
if (!$insertDB) {
|
||||||
$message .= " Mail: $dbError";
|
$message .= " Mail: $dbError";
|
||||||
}
|
}
|
||||||
|
|
||||||
error_log($message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$sendMail && !$insertDB) {
|
if (!$sendMail && !$insertDB) {
|
||||||
|
@ -182,13 +182,14 @@ namespace Api\Mail {
|
|||||||
$this->success = @$mail->Send();
|
$this->success = @$mail->Send();
|
||||||
if (!$this->success) {
|
if (!$this->success) {
|
||||||
$this->lastError = "Error sending Mail: $mail->ErrorInfo";
|
$this->lastError = "Error sending Mail: $mail->ErrorInfo";
|
||||||
error_log("sendMail() failed: $mail->ErrorInfo");
|
$this->logger->error("sendMail() failed: $mail->ErrorInfo");
|
||||||
} else {
|
} else {
|
||||||
$this->result["messageId"] = $mail->getLastMessageID();
|
$this->result["messageId"] = $mail->getLastMessageID();
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->success = false;
|
$this->success = false;
|
||||||
$this->lastError = "Error sending Mail: $e";
|
$this->lastError = "Error sending Mail: $e";
|
||||||
|
$this->logger->error($this->lastError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
|
@ -6,6 +6,12 @@ use Driver\Logger\Logger;
|
|||||||
use Objects\User;
|
use Objects\User;
|
||||||
use PhpMqtt\Client\MqttClient;
|
use PhpMqtt\Client\MqttClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: we need following features, probably as abstract/generic class/method:
|
||||||
|
* - easy way for pagination (select with limit/offset)
|
||||||
|
* - CRUD Endpoints/Objects (Create, Update, Delete)
|
||||||
|
*/
|
||||||
|
|
||||||
abstract class Request {
|
abstract class Request {
|
||||||
|
|
||||||
protected User $user;
|
protected User $user;
|
||||||
|
@ -35,7 +35,7 @@ class Stats extends Request {
|
|||||||
return ($this->success ? $res[0]["count"] : 0);
|
return ($this->success ? $res[0]["count"] : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkSettings() {
|
private function checkSettings(): bool {
|
||||||
$req = new \Api\Settings\Get($this->user);
|
$req = new \Api\Settings\Get($this->user);
|
||||||
$this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$"));
|
$this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$"));
|
||||||
$this->lastError = $req->getLastError();
|
$this->lastError = $req->getLastError();
|
||||||
|
@ -33,12 +33,12 @@ class VerifyCaptcha extends Request {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL,$url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
curl_setopt($ch, CURLOPT_POST, 1);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
$response = @json_decode(curl_exec($ch), true);
|
$response = @json_decode(curl_exec($ch), true);
|
||||||
curl_close ($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
$this->success = false;
|
$this->success = false;
|
||||||
$this->lastError = "Could not verify captcha: No response from google received.";
|
$this->lastError = "Could not verify captcha: No response from google received.";
|
||||||
@ -49,10 +49,9 @@ class VerifyCaptcha extends Request {
|
|||||||
$this->lastError = "Could not verify captcha: " . implode(";", $response["error-codes"]);
|
$this->lastError = "Could not verify captcha: " . implode(";", $response["error-codes"]);
|
||||||
} else {
|
} else {
|
||||||
$score = $response["score"];
|
$score = $response["score"];
|
||||||
if($action !== $response["action"]) {
|
if ($action !== $response["action"]) {
|
||||||
$this->createError("Could not verify captcha: Action does not match");
|
$this->createError("Could not verify captcha: Action does not match");
|
||||||
}
|
} else if ($score < 0.7) {
|
||||||
else if($score < 0.7) {
|
|
||||||
$this->createError("Could not verify captcha: Google ReCaptcha Score < 0.7 (Your score: $score), you are likely a bot");
|
$this->createError("Could not verify captcha: Google ReCaptcha Score < 0.7 (Your score: $score), you are likely a bot");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,6 @@ use \Driver\SQL\Strategy\CascadeStrategy;
|
|||||||
|
|
||||||
class CreateDatabase extends DatabaseScript {
|
class CreateDatabase extends DatabaseScript {
|
||||||
|
|
||||||
// NOTE:
|
|
||||||
// explicit serial ids removed due to postgres' serial implementation
|
|
||||||
|
|
||||||
public static function createQueries(SQL $sql): array {
|
public static function createQueries(SQL $sql): array {
|
||||||
$queries = array();
|
$queries = array();
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Driver\SQL\Query;
|
namespace Driver\SQL\Column;
|
||||||
|
|
||||||
use Driver\SQL\Column\IntColumn;
|
|
||||||
|
|
||||||
class BigIntColumn extends IntColumn {
|
class BigIntColumn extends IntColumn {
|
||||||
|
|
10
core/Driver/SQL/Column/DoubleColumn.class.php
Normal file
10
core/Driver/SQL/Column/DoubleColumn.class.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Driver\SQL\Column;
|
||||||
|
|
||||||
|
class DoubleColumn extends NumericColumn {
|
||||||
|
public function __construct(string $name, bool $nullable, $defaultValue = NULL, ?int $totalDigits = null, ?int $decimalDigits = null) {
|
||||||
|
parent::__construct($name, $nullable, $defaultValue, $totalDigits, $decimalDigits);
|
||||||
|
$this->type = "DOUBLE";
|
||||||
|
}
|
||||||
|
}
|
10
core/Driver/SQL/Column/FloatColumn.class.php
Normal file
10
core/Driver/SQL/Column/FloatColumn.class.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Driver\SQL\Column;
|
||||||
|
|
||||||
|
class FloatColumn extends NumericColumn {
|
||||||
|
public function __construct(string $name, bool $nullable, $defaultValue = NULL, ?int $totalDigits = null, ?int $decimalDigits = null) {
|
||||||
|
parent::__construct($name, $nullable, $defaultValue, $totalDigits, $decimalDigits);
|
||||||
|
$this->type = "FLOAT";
|
||||||
|
}
|
||||||
|
}
|
31
core/Driver/SQL/Column/NumericColumn.class.php
Normal file
31
core/Driver/SQL/Column/NumericColumn.class.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Driver\SQL\Column;
|
||||||
|
|
||||||
|
use Driver\SQL\Column\Column;
|
||||||
|
|
||||||
|
class NumericColumn extends Column {
|
||||||
|
|
||||||
|
protected string $type;
|
||||||
|
private ?int $totalDigits;
|
||||||
|
private ?int $decimalDigits;
|
||||||
|
|
||||||
|
public function __construct(string $name, bool $nullable, $defaultValue = NULL, ?int $totalDigits = null, ?int $decimalDigits = null) {
|
||||||
|
parent::__construct($name, $nullable, $defaultValue);
|
||||||
|
$this->totalDigits = $totalDigits;
|
||||||
|
$this->decimalDigits = $decimalDigits;
|
||||||
|
$this->type = "NUMERIC";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDecimalDigits(): ?int {
|
||||||
|
return $this->decimalDigits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalDigits(): ?int {
|
||||||
|
return $this->totalDigits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeName(): string {
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ use \Api\Parameter\Parameter;
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use \Driver\SQL\Column\Column;
|
use \Driver\SQL\Column\Column;
|
||||||
use \Driver\SQL\Column\IntColumn;
|
use \Driver\SQL\Column\IntColumn;
|
||||||
|
use Driver\SQL\Column\NumericColumn;
|
||||||
use \Driver\SQL\Column\SerialColumn;
|
use \Driver\SQL\Column\SerialColumn;
|
||||||
use \Driver\SQL\Column\StringColumn;
|
use \Driver\SQL\Column\StringColumn;
|
||||||
use \Driver\SQL\Column\EnumColumn;
|
use \Driver\SQL\Column\EnumColumn;
|
||||||
@ -267,6 +268,19 @@ class MySQL extends SQL {
|
|||||||
return "BOOLEAN";
|
return "BOOLEAN";
|
||||||
} else if ($column instanceof JsonColumn) {
|
} else if ($column instanceof JsonColumn) {
|
||||||
return "LONGTEXT"; # some maria db setups don't allow JSON here…
|
return "LONGTEXT"; # some maria db setups don't allow JSON here…
|
||||||
|
} else if ($column instanceof NumericColumn) {
|
||||||
|
$digitsTotal = $column->getTotalDigits();
|
||||||
|
$digitsDecimal = $column->getDecimalDigits();
|
||||||
|
$type = $column->getTypeName();
|
||||||
|
if ($digitsTotal !== null) {
|
||||||
|
if ($digitsDecimal !== null) {
|
||||||
|
return "$type($digitsTotal,$digitsDecimal)";
|
||||||
|
} else {
|
||||||
|
return "$type($digitsTotal)";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -6,6 +6,7 @@ use \Api\Parameter\Parameter;
|
|||||||
|
|
||||||
use Driver\SQL\Column\Column;
|
use Driver\SQL\Column\Column;
|
||||||
use \Driver\SQL\Column\IntColumn;
|
use \Driver\SQL\Column\IntColumn;
|
||||||
|
use Driver\SQL\Column\NumericColumn;
|
||||||
use \Driver\SQL\Column\SerialColumn;
|
use \Driver\SQL\Column\SerialColumn;
|
||||||
use \Driver\SQL\Column\StringColumn;
|
use \Driver\SQL\Column\StringColumn;
|
||||||
use \Driver\SQL\Column\EnumColumn;
|
use \Driver\SQL\Column\EnumColumn;
|
||||||
@ -46,7 +47,7 @@ class PostgreSQL extends SQL {
|
|||||||
|
|
||||||
// Connection Management
|
// Connection Management
|
||||||
public function connect() {
|
public function connect() {
|
||||||
if(!is_null($this->connection)) {
|
if (!is_null($this->connection)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ class PostgreSQL extends SQL {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$connectionString = array();
|
$connectionString = array();
|
||||||
foreach($config as $key => $val) {
|
foreach ($config as $key => $val) {
|
||||||
if (!empty($val)) {
|
if (!empty($val)) {
|
||||||
$connectionString[] = "$key=$val";
|
$connectionString[] = "$key=$val";
|
||||||
}
|
}
|
||||||
@ -77,7 +78,7 @@ class PostgreSQL extends SQL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function disconnect() {
|
public function disconnect() {
|
||||||
if(is_null($this->connection))
|
if (is_null($this->connection))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@pg_close($this->connection);
|
@pg_close($this->connection);
|
||||||
@ -102,9 +103,9 @@ class PostgreSQL extends SQL {
|
|||||||
$pgParams = array();
|
$pgParams = array();
|
||||||
|
|
||||||
if (!is_null($values)) {
|
if (!is_null($values)) {
|
||||||
foreach($values as $value) {
|
foreach ($values as $value) {
|
||||||
$paramType = Parameter::parseType($value);
|
$paramType = Parameter::parseType($value);
|
||||||
switch($paramType) {
|
switch ($paramType) {
|
||||||
case Parameter::TYPE_DATE:
|
case Parameter::TYPE_DATE:
|
||||||
$value = $value->format("Y-m-d");
|
$value = $value->format("Y-m-d");
|
||||||
break;
|
break;
|
||||||
@ -157,7 +158,7 @@ class PostgreSQL extends SQL {
|
|||||||
if (!is_null($strategy)) {
|
if (!is_null($strategy)) {
|
||||||
if ($strategy instanceof UpdateStrategy) {
|
if ($strategy instanceof UpdateStrategy) {
|
||||||
$updateValues = array();
|
$updateValues = array();
|
||||||
foreach($strategy->getValues() as $key => $value) {
|
foreach ($strategy->getValues() as $key => $value) {
|
||||||
$leftColumn = $this->columnName($key);
|
$leftColumn = $this->columnName($key);
|
||||||
if ($value instanceof Column) {
|
if ($value instanceof Column) {
|
||||||
$columnName = $this->columnName($value->getName());
|
$columnName = $this->columnName($value->getName());
|
||||||
@ -205,7 +206,7 @@ class PostgreSQL extends SQL {
|
|||||||
// UGLY but.. what should i do?
|
// UGLY but.. what should i do?
|
||||||
private function createEnum(EnumColumn $enumColumn, string $typeName): string {
|
private function createEnum(EnumColumn $enumColumn, string $typeName): string {
|
||||||
$values = array();
|
$values = array();
|
||||||
foreach($enumColumn->getValues() as $value) {
|
foreach ($enumColumn->getValues() as $value) {
|
||||||
$values[] = $this->getValueDefinition($value);
|
$values[] = $this->getValueDefinition($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,22 +229,33 @@ class PostgreSQL extends SQL {
|
|||||||
} else {
|
} else {
|
||||||
return "TEXT";
|
return "TEXT";
|
||||||
}
|
}
|
||||||
} else if($column instanceof SerialColumn) {
|
} else if ($column instanceof SerialColumn) {
|
||||||
return "SERIAL";
|
return "SERIAL";
|
||||||
} else if($column instanceof IntColumn) {
|
} else if ($column instanceof IntColumn) {
|
||||||
return $column->getType();
|
return $column->getType();
|
||||||
} else if($column instanceof DateTimeColumn) {
|
} else if ($column instanceof DateTimeColumn) {
|
||||||
return "TIMESTAMP";
|
return "TIMESTAMP";
|
||||||
} else if($column instanceof EnumColumn) {
|
} else if ($column instanceof EnumColumn) {
|
||||||
$typeName = $column->getName();
|
$typeName = $column->getName();
|
||||||
if(!endsWith($typeName, "_type")) {
|
if (!endsWith($typeName, "_type")) {
|
||||||
$typeName = "${typeName}_type";
|
$typeName = "${typeName}_type";
|
||||||
}
|
}
|
||||||
return $typeName;
|
return $typeName;
|
||||||
} else if($column instanceof BoolColumn) {
|
} else if ($column instanceof BoolColumn) {
|
||||||
return "BOOLEAN";
|
return "BOOLEAN";
|
||||||
} else if($column instanceof JsonColumn) {
|
} else if ($column instanceof JsonColumn) {
|
||||||
return "JSON";
|
return "JSON";
|
||||||
|
} else if ($column instanceof NumericColumn) {
|
||||||
|
$digitsDecimal = $column->getDecimalDigits();
|
||||||
|
$type = $column->getTypeName();
|
||||||
|
if ($digitsDecimal !== null) {
|
||||||
|
if ($type === "double") {
|
||||||
|
$type = "float"; // postgres doesn't know about double :/
|
||||||
|
}
|
||||||
|
return "$type($digitsDecimal)";
|
||||||
|
} else {
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
$this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column));
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -274,9 +286,9 @@ class PostgreSQL extends SQL {
|
|||||||
protected function getValueDefinition($value) {
|
protected function getValueDefinition($value) {
|
||||||
if (is_numeric($value)) {
|
if (is_numeric($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
} else if(is_bool($value)) {
|
} else if (is_bool($value)) {
|
||||||
return $value ? "TRUE" : "FALSE";
|
return $value ? "TRUE" : "FALSE";
|
||||||
} else if(is_null($value)) {
|
} else if (is_null($value)) {
|
||||||
return "NULL";
|
return "NULL";
|
||||||
} else if ($value instanceof Keyword) {
|
} else if ($value instanceof Keyword) {
|
||||||
return $value->getValue();
|
return $value->getValue();
|
||||||
@ -312,7 +324,7 @@ class PostgreSQL extends SQL {
|
|||||||
public function tableName($table): string {
|
public function tableName($table): string {
|
||||||
if (is_array($table)) {
|
if (is_array($table)) {
|
||||||
$tables = array();
|
$tables = array();
|
||||||
foreach($table as $t) $tables[] = $this->tableName($t);
|
foreach ($table as $t) $tables[] = $this->tableName($t);
|
||||||
return implode(",", $tables);
|
return implode(",", $tables);
|
||||||
} else {
|
} else {
|
||||||
$parts = explode(" ", $table);
|
$parts = explode(" ", $table);
|
||||||
@ -328,15 +340,17 @@ class PostgreSQL extends SQL {
|
|||||||
public function columnName($col): string {
|
public function columnName($col): string {
|
||||||
if ($col instanceof KeyWord) {
|
if ($col instanceof KeyWord) {
|
||||||
return $col->getValue();
|
return $col->getValue();
|
||||||
} elseif(is_array($col)) {
|
} elseif (is_array($col)) {
|
||||||
$columns = array_map(function ($c) { return $this->columnName($c); }, $col);
|
$columns = array_map(function ($c) {
|
||||||
|
return $this->columnName($c);
|
||||||
|
}, $col);
|
||||||
return implode(",", $columns);
|
return implode(",", $columns);
|
||||||
} else {
|
} else {
|
||||||
if (($index = strrpos($col, ".")) !== FALSE) {
|
if (($index = strrpos($col, ".")) !== FALSE) {
|
||||||
$tableName = $this->tableName(substr($col, 0, $index));
|
$tableName = $this->tableName(substr($col, 0, $index));
|
||||||
$columnName = $this->columnName(substr($col, $index + 1));
|
$columnName = $this->columnName(substr($col, $index + 1));
|
||||||
return "$tableName.$columnName";
|
return "$tableName.$columnName";
|
||||||
} else if(($index = stripos($col, " as ")) !== FALSE) {
|
} else if (($index = stripos($col, " as ")) !== FALSE) {
|
||||||
$columnName = $this->columnName(trim(substr($col, 0, $index)));
|
$columnName = $this->columnName(trim(substr($col, 0, $index)));
|
||||||
$alias = $this->columnName(trim(substr($col, $index + 4)));
|
$alias = $this->columnName(trim(substr($col, $index + 4)));
|
||||||
return "$columnName as $alias";
|
return "$columnName as $alias";
|
||||||
@ -358,7 +372,7 @@ class PostgreSQL extends SQL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function buildCondition($condition, &$params) {
|
public function buildCondition($condition, &$params) {
|
||||||
if($condition instanceof CondRegex) {
|
if ($condition instanceof CondRegex) {
|
||||||
$left = $condition->getLeftExp();
|
$left = $condition->getLeftExp();
|
||||||
$right = $condition->getRightExp();
|
$right = $condition->getRightExp();
|
||||||
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
$left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params);
|
||||||
@ -372,7 +386,7 @@ class PostgreSQL extends SQL {
|
|||||||
private function createTriggerProcedure(string $name, array $statements) {
|
private function createTriggerProcedure(string $name, array $statements) {
|
||||||
$params = [];
|
$params = [];
|
||||||
$query = "CREATE OR REPLACE FUNCTION $name() RETURNS TRIGGER AS \$table\$ BEGIN ";
|
$query = "CREATE OR REPLACE FUNCTION $name() RETURNS TRIGGER AS \$table\$ BEGIN ";
|
||||||
foreach($statements as $stmt) {
|
foreach ($statements as $stmt) {
|
||||||
if ($stmt instanceof Keyword) {
|
if ($stmt instanceof Keyword) {
|
||||||
$query .= $stmt->getValue() . ";";
|
$query .= $stmt->getValue() . ";";
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace Driver\SQL\Query;
|
namespace Driver\SQL\Query;
|
||||||
|
|
||||||
|
use Driver\SQL\Column\BigIntColumn;
|
||||||
|
use Driver\SQL\Column\Column;
|
||||||
|
use Driver\SQL\Column\DoubleColumn;
|
||||||
|
use Driver\SQL\Column\FloatColumn;
|
||||||
|
use Driver\SQL\Column\NumericColumn;
|
||||||
use Driver\SQL\Column\SerialColumn;
|
use Driver\SQL\Column\SerialColumn;
|
||||||
use Driver\SQL\Column\StringColumn;
|
use Driver\SQL\Column\StringColumn;
|
||||||
use Driver\SQL\Column\IntColumn;
|
use Driver\SQL\Column\IntColumn;
|
||||||
@ -10,6 +15,7 @@ use Driver\SQL\Column\EnumColumn;
|
|||||||
use Driver\SQL\Column\BoolColumn;
|
use Driver\SQL\Column\BoolColumn;
|
||||||
use Driver\SQL\Column\JsonColumn;
|
use Driver\SQL\Column\JsonColumn;
|
||||||
|
|
||||||
|
use Driver\SQL\Constraint\Constraint;
|
||||||
use Driver\SQL\Constraint\PrimaryKey;
|
use Driver\SQL\Constraint\PrimaryKey;
|
||||||
use Driver\SQL\Constraint\Unique;
|
use Driver\SQL\Constraint\Unique;
|
||||||
use Driver\SQL\Constraint\ForeignKey;
|
use Driver\SQL\Constraint\ForeignKey;
|
||||||
@ -31,6 +37,16 @@ class CreateTable extends Query {
|
|||||||
$this->ifNotExists = false;
|
$this->ifNotExists = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addColumn(Column $column): CreateTable {
|
||||||
|
$this->columns[$column->getName()] = $column;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addConstraint(Constraint $constraint): CreateTable {
|
||||||
|
$this->constraints[] = $constraint;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function addSerial(string $name): CreateTable {
|
public function addSerial(string $name): CreateTable {
|
||||||
$this->columns[$name] = new SerialColumn($name);
|
$this->columns[$name] = new SerialColumn($name);
|
||||||
return $this;
|
return $this;
|
||||||
@ -71,6 +87,21 @@ class CreateTable extends Query {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addNumeric(string $name, bool $nullable = false, $defaultValue = NULL, ?int $digitsTotal = 10, ?int $digitsDecimal = 0): CreateTable {
|
||||||
|
$this->columns[$name] = new NumericColumn($name, $nullable, $defaultValue, $digitsTotal, $digitsDecimal);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFloat(string $name, bool $nullable = false, $defaultValue = NULL, ?int $digitsTotal = null, ?int $digitsDecimal = null): CreateTable {
|
||||||
|
$this->columns[$name] = new FloatColumn($name, $nullable, $defaultValue, $digitsTotal, $digitsDecimal);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addDouble(string $name, bool $nullable = false, $defaultValue = NULL, ?int $digitsTotal = null, ?int $digitsDecimal = null): CreateTable {
|
||||||
|
$this->columns[$name] = new DoubleColumn($name, $nullable, $defaultValue, $digitsTotal, $digitsDecimal);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function primaryKey(...$names): CreateTable {
|
public function primaryKey(...$names): CreateTable {
|
||||||
$this->constraints[] = new PrimaryKey($names);
|
$this->constraints[] = new PrimaryKey($names);
|
||||||
return $this;
|
return $this;
|
||||||
|
75
core/Objects/DatabaseEntity/DatabaseEntity.class.php
Normal file
75
core/Objects/DatabaseEntity/DatabaseEntity.class.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Objects\DatabaseEntity;
|
||||||
|
|
||||||
|
use Driver\Logger\Logger;
|
||||||
|
use Driver\SQL\Condition\Condition;
|
||||||
|
use Driver\SQL\SQL;
|
||||||
|
|
||||||
|
abstract class DatabaseEntity {
|
||||||
|
|
||||||
|
private static array $handlers = [];
|
||||||
|
private ?int $id;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->id = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function find(SQL $sql, int $id): ?DatabaseEntity {
|
||||||
|
$handler = self::getHandler();
|
||||||
|
return $handler->fetchOne($sql, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findAll(SQL $sql, ?Condition $condition): ?array {
|
||||||
|
$handler = self::getHandler();
|
||||||
|
return $handler->fetchMultiple($sql, $condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(SQL $sql): bool {
|
||||||
|
$handler = self::getHandler();
|
||||||
|
$res = $handler->insertOrUpdate($sql, $this);
|
||||||
|
if ($res === false) {
|
||||||
|
return false;
|
||||||
|
} else if ($this->id === null) {
|
||||||
|
$this->id = $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(SQL $sql): bool {
|
||||||
|
$handler = self::getHandler();
|
||||||
|
if ($this->id === null) {
|
||||||
|
$className = $handler->getReflection()->getName();
|
||||||
|
(new Logger("DatabaseEntity", $sql))->error("Cannot delete entity of class '$className' without id");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler->delete($sql, $this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getHandler($obj_or_class = null): DatabaseEntityHandler {
|
||||||
|
|
||||||
|
if (!$obj_or_class) {
|
||||||
|
$obj_or_class = get_called_class();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($obj_or_class instanceof \ReflectionClass)) {
|
||||||
|
$class = new \ReflectionClass($obj_or_class);
|
||||||
|
} else {
|
||||||
|
$class = $obj_or_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
$handler = self::$handlers[$class->getShortName()] ?? null;
|
||||||
|
if (!$handler) {
|
||||||
|
$handler = new DatabaseEntityHandler($class);
|
||||||
|
self::$handlers[$class->getShortName()] = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
188
core/Objects/DatabaseEntity/DatabaseEntityHandler.php
Normal file
188
core/Objects/DatabaseEntity/DatabaseEntityHandler.php
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Objects\DatabaseEntity;
|
||||||
|
|
||||||
|
use Driver\SQL\Column\BoolColumn;
|
||||||
|
use Driver\SQL\Column\DateTimeColumn;
|
||||||
|
use Driver\SQL\Column\IntColumn;
|
||||||
|
use Driver\SQL\Column\StringColumn;
|
||||||
|
use Driver\SQL\Condition\Compare;
|
||||||
|
use Driver\SQL\Condition\Condition;
|
||||||
|
use Driver\SQL\Column\DoubleColumn;
|
||||||
|
use Driver\SQL\Column\FloatColumn;
|
||||||
|
use Driver\SQL\Constraint\ForeignKey;
|
||||||
|
use Driver\SQL\SQL;
|
||||||
|
use Driver\SQL\Strategy\CascadeStrategy;
|
||||||
|
use Driver\SQL\Strategy\SetNullStrategy;
|
||||||
|
use PHPUnit\Util\Exception;
|
||||||
|
|
||||||
|
class DatabaseEntityHandler {
|
||||||
|
|
||||||
|
private \ReflectionClass $entityClass;
|
||||||
|
private string $tableName;
|
||||||
|
private array $columns;
|
||||||
|
private array $properties;
|
||||||
|
|
||||||
|
public function __construct(\ReflectionClass $entityClass) {
|
||||||
|
$className = $entityClass->getName();
|
||||||
|
$this->entityClass = $entityClass;
|
||||||
|
if (!$this->entityClass->isSubclassOf(DatabaseEntity::class) ||
|
||||||
|
!$this->entityClass->isInstantiable()) {
|
||||||
|
throw new Exception("Cannot persist class '$className': Not an instance of DatabaseEntity or not instantiable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tableName = $this->entityClass->getShortName();
|
||||||
|
$this->columns = [];
|
||||||
|
$this->properties = [];
|
||||||
|
$this->relations = [];
|
||||||
|
|
||||||
|
foreach ($this->entityClass->getProperties() as $property) {
|
||||||
|
$propertyName = $property->getName();
|
||||||
|
$propertyType = $property->getType();
|
||||||
|
$columnName = self::getColumnName($propertyName);
|
||||||
|
if (!($propertyType instanceof \ReflectionNamedType)) {
|
||||||
|
throw new Exception("Cannot persist class '$className': Property '$propertyName' has no valid type");
|
||||||
|
}
|
||||||
|
|
||||||
|
$nullable = $propertyType->allowsNull();
|
||||||
|
$propertyTypeName = $propertyType->getName();
|
||||||
|
if ($propertyTypeName === 'string') {
|
||||||
|
$this->columns[$propertyName] = new StringColumn($columnName, null, $nullable);
|
||||||
|
} else if ($propertyTypeName === 'int') {
|
||||||
|
$this->columns[$propertyName] = new IntColumn($columnName, $nullable);
|
||||||
|
} else if ($propertyTypeName === 'float') {
|
||||||
|
$this->columns[$propertyName] = new FloatColumn($columnName, $nullable);
|
||||||
|
} else if ($propertyTypeName === 'double') {
|
||||||
|
$this->columns[$propertyName] = new DoubleColumn($columnName, $nullable);
|
||||||
|
} else if ($propertyTypeName === 'bool') {
|
||||||
|
$this->columns[$propertyName] = new BoolColumn($columnName, $nullable);
|
||||||
|
} else if ($propertyTypeName === 'DateTime') {
|
||||||
|
$this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$requestedClass = new \ReflectionClass($propertyTypeName);
|
||||||
|
if ($requestedClass->isSubclassOf(DatabaseEntity::class)) {
|
||||||
|
$requestedHandler = ($requestedClass->getName() === $this->entityClass->getName()) ?
|
||||||
|
$this : DatabaseEntity::getHandler($requestedClass);
|
||||||
|
$strategy = $nullable ? new SetNullStrategy() : new CascadeStrategy();
|
||||||
|
$this->columns[$propertyName] = new IntColumn($columnName, $nullable);
|
||||||
|
$this->relations[$propertyName] = new ForeignKey($columnName, $requestedHandler->tableName, "id", $strategy);
|
||||||
|
}
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
throw new Exception("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->properties[$propertyName] = $property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getColumnName(string $propertyName): string {
|
||||||
|
// abcTestLOL => abc_test_lol
|
||||||
|
return strtolower(preg_replace_callback("/([a-z])([A-Z]+)/", function ($m) {
|
||||||
|
return $m[1] . "_" . strtolower($m[2]);
|
||||||
|
}, $propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReflection(): \ReflectionClass {
|
||||||
|
return $this->entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function entityFromRow(array $row): DatabaseEntity {
|
||||||
|
$entity = $this->entityClass->newInstanceWithoutConstructor();
|
||||||
|
foreach ($this->columns as $propertyName => $column) {
|
||||||
|
$this->properties[$propertyName]->setValue($entity, $row[$column]);
|
||||||
|
}
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchOne(SQL $sql, int $id): ?DatabaseEntity {
|
||||||
|
$res = $sql->select(...array_keys($this->columns))
|
||||||
|
->from($this->tableName)
|
||||||
|
->where(new Compare("id", $id))
|
||||||
|
->first()
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if (empty($res)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return $this->entityFromRow($res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchMultiple(SQL $sql, ?Condition $condition = null): ?array {
|
||||||
|
$query = $sql->select(...array_keys($this->columns))
|
||||||
|
->from($this->tableName);
|
||||||
|
|
||||||
|
if ($condition) {
|
||||||
|
$query->where($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $query->execute();
|
||||||
|
if ($res === false) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
$entities = [];
|
||||||
|
foreach ($res as $row) {
|
||||||
|
$entities[] = $this->entityFromRow($row);
|
||||||
|
}
|
||||||
|
return $entities;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createTable(SQL $sql): bool {
|
||||||
|
$query = $sql->createTable($this->tableName)
|
||||||
|
->onlyIfNotExists()
|
||||||
|
->addSerial("id")
|
||||||
|
->primaryKey("id");
|
||||||
|
|
||||||
|
foreach ($this->columns as $column) {
|
||||||
|
$query->addColumn($column);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->relations as $constraint) {
|
||||||
|
$query->addConstraint($constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertOrUpdate(SQL $sql, DatabaseEntity $entity) {
|
||||||
|
$id = $entity->getId();
|
||||||
|
if ($id === null) {
|
||||||
|
$columns = [];
|
||||||
|
$row = [];
|
||||||
|
|
||||||
|
foreach ($this->columns as $propertyName => $column) {
|
||||||
|
$columns[] = $column->getName();
|
||||||
|
$row[] = $this->properties[$propertyName]->getValue($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $sql->insert($this->tableName, $columns)
|
||||||
|
->addRow(...$row)
|
||||||
|
->returning("id")
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if ($res !== false) {
|
||||||
|
return $sql->getLastInsertId();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$query = $sql->update($this->tableName)
|
||||||
|
->where(new Compare("id", $id));
|
||||||
|
|
||||||
|
foreach ($this->columns as $propertyName => $column) {
|
||||||
|
$columnName = $column->getName();
|
||||||
|
$value = $this->properties[$propertyName]->getValue($entity);
|
||||||
|
$query->set($columnName, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(SQL $sql, int $id) {
|
||||||
|
return $sql->delete($this->tableName)->where(new Compare("id", $id))->execute();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains functions used globally without a namespace and should not require
|
||||||
|
* any other files. It also loads the composer vendor libraries.
|
||||||
|
*/
|
||||||
|
|
||||||
$autoLoad = implode(DIRECTORY_SEPARATOR, [__DIR__, "External", "vendor", "autoload.php"]);
|
$autoLoad = implode(DIRECTORY_SEPARATOR, [__DIR__, "External", "vendor", "autoload.php"]);
|
||||||
if (is_file($autoLoad)) {
|
if (is_file($autoLoad)) {
|
||||||
require_once $autoLoad;
|
require_once $autoLoad;
|
||||||
@ -186,10 +191,6 @@ function intendCode($code, $escape = true): string {
|
|||||||
return $newCode;
|
return $newCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceCssSelector($sel) {
|
|
||||||
return preg_replace("~[.#<>]~", "_", preg_replace("~[:\-]~", "", $sel));
|
|
||||||
}
|
|
||||||
|
|
||||||
function html_attributes(array $attributes): string {
|
function html_attributes(array $attributes): string {
|
||||||
return implode(" ", array_map(function ($key) use ($attributes) {
|
return implode(" ", array_map(function ($key) use ($attributes) {
|
||||||
$value = htmlspecialchars($attributes[$key]);
|
$value = htmlspecialchars($attributes[$key]);
|
||||||
|
0
test/DatabaseEntity.test.php
Normal file
0
test/DatabaseEntity.test.php
Normal file
Loading…
Reference in New Issue
Block a user