diff --git a/core/Api/ContactAPI.class.php b/core/Api/ContactAPI.class.php index eed1bbe..599a3ac 100644 --- a/core/Api/ContactAPI.class.php +++ b/core/Api/ContactAPI.class.php @@ -95,8 +95,6 @@ namespace Api\Contact { if (!$insertDB) { $message .= " Mail: $dbError"; } - - error_log($message); } if (!$sendMail && !$insertDB) { diff --git a/core/Api/MailAPI.class.php b/core/Api/MailAPI.class.php index c8d7f72..ac9a5a8 100644 --- a/core/Api/MailAPI.class.php +++ b/core/Api/MailAPI.class.php @@ -182,13 +182,14 @@ namespace Api\Mail { $this->success = @$mail->Send(); if (!$this->success) { $this->lastError = "Error sending Mail: $mail->ErrorInfo"; - error_log("sendMail() failed: $mail->ErrorInfo"); + $this->logger->error("sendMail() failed: $mail->ErrorInfo"); } else { $this->result["messageId"] = $mail->getLastMessageID(); } } catch (Exception $e) { $this->success = false; $this->lastError = "Error sending Mail: $e"; + $this->logger->error($this->lastError); } return $this->success; diff --git a/core/Api/Request.class.php b/core/Api/Request.class.php index 4f94ea4..b10ed49 100644 --- a/core/Api/Request.class.php +++ b/core/Api/Request.class.php @@ -6,6 +6,12 @@ use Driver\Logger\Logger; use Objects\User; 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 { protected User $user; diff --git a/core/Api/Stats.class.php b/core/Api/Stats.class.php index 815611a..ebfb61d 100644 --- a/core/Api/Stats.class.php +++ b/core/Api/Stats.class.php @@ -35,7 +35,7 @@ class Stats extends Request { return ($this->success ? $res[0]["count"] : 0); } - private function checkSettings() { + private function checkSettings(): bool { $req = new \Api\Settings\Get($this->user); $this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$")); $this->lastError = $req->getLastError(); diff --git a/core/Api/VerifyCaptcha.class.php b/core/Api/VerifyCaptcha.class.php index f702d2c..2345ab3 100644 --- a/core/Api/VerifyCaptcha.class.php +++ b/core/Api/VerifyCaptcha.class.php @@ -17,10 +17,10 @@ class VerifyCaptcha extends Request { } public function _execute(): bool { - $settings = $this->user->getConfiguration()->getSettings(); - if (!$settings->isRecaptchaEnabled()) { - return $this->createError("Google reCaptcha is not enabled."); - } + $settings = $this->user->getConfiguration()->getSettings(); + if (!$settings->isRecaptchaEnabled()) { + return $this->createError("Google reCaptcha is not enabled."); + } $url = "https://www.google.com/recaptcha/api/siteverify"; $secret = $settings->getRecaptchaSecretKey(); @@ -33,12 +33,12 @@ class VerifyCaptcha extends Request { ); $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_POSTFIELDS, http_build_query($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = @json_decode(curl_exec($ch), true); - curl_close ($ch); + curl_close($ch); $this->success = false; $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"]); } else { $score = $response["score"]; - if($action !== $response["action"]) { + if ($action !== $response["action"]) { $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"); } } diff --git a/core/Configuration/CreateDatabase.class.php b/core/Configuration/CreateDatabase.class.php index 2720139..73869b7 100644 --- a/core/Configuration/CreateDatabase.class.php +++ b/core/Configuration/CreateDatabase.class.php @@ -8,9 +8,6 @@ use \Driver\SQL\Strategy\CascadeStrategy; class CreateDatabase extends DatabaseScript { - // NOTE: - // explicit serial ids removed due to postgres' serial implementation - public static function createQueries(SQL $sql): array { $queries = array(); diff --git a/core/Driver/SQL/Column/BigIntColumn.php b/core/Driver/SQL/Column/BigIntColumn.class.php similarity index 79% rename from core/Driver/SQL/Column/BigIntColumn.php rename to core/Driver/SQL/Column/BigIntColumn.class.php index d63fec2..a065f31 100644 --- a/core/Driver/SQL/Column/BigIntColumn.php +++ b/core/Driver/SQL/Column/BigIntColumn.class.php @@ -1,8 +1,6 @@ type = "DOUBLE"; + } +} \ No newline at end of file diff --git a/core/Driver/SQL/Column/FloatColumn.class.php b/core/Driver/SQL/Column/FloatColumn.class.php new file mode 100644 index 0000000..fa7408c --- /dev/null +++ b/core/Driver/SQL/Column/FloatColumn.class.php @@ -0,0 +1,10 @@ +type = "FLOAT"; + } +} \ No newline at end of file diff --git a/core/Driver/SQL/Column/NumericColumn.class.php b/core/Driver/SQL/Column/NumericColumn.class.php new file mode 100644 index 0000000..6c92eb0 --- /dev/null +++ b/core/Driver/SQL/Column/NumericColumn.class.php @@ -0,0 +1,31 @@ +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; + } +} \ No newline at end of file diff --git a/core/Driver/SQL/MySQL.class.php b/core/Driver/SQL/MySQL.class.php index e97d4f4..fff0bb6 100644 --- a/core/Driver/SQL/MySQL.class.php +++ b/core/Driver/SQL/MySQL.class.php @@ -7,6 +7,7 @@ use \Api\Parameter\Parameter; use DateTime; use \Driver\SQL\Column\Column; use \Driver\SQL\Column\IntColumn; +use Driver\SQL\Column\NumericColumn; use \Driver\SQL\Column\SerialColumn; use \Driver\SQL\Column\StringColumn; use \Driver\SQL\Column\EnumColumn; @@ -267,6 +268,19 @@ class MySQL extends SQL { return "BOOLEAN"; } else if ($column instanceof JsonColumn) { 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 { $this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column)); return NULL; diff --git a/core/Driver/SQL/PostgreSQL.class.php b/core/Driver/SQL/PostgreSQL.class.php index 5a53943..35f5733 100644 --- a/core/Driver/SQL/PostgreSQL.class.php +++ b/core/Driver/SQL/PostgreSQL.class.php @@ -6,6 +6,7 @@ use \Api\Parameter\Parameter; use Driver\SQL\Column\Column; use \Driver\SQL\Column\IntColumn; +use Driver\SQL\Column\NumericColumn; use \Driver\SQL\Column\SerialColumn; use \Driver\SQL\Column\StringColumn; use \Driver\SQL\Column\EnumColumn; @@ -33,7 +34,7 @@ use Driver\SQL\Type\Trigger; class PostgreSQL extends SQL { public function __construct($connectionData) { - parent::__construct($connectionData); + parent::__construct($connectionData); } public function checkRequirements() { @@ -46,7 +47,7 @@ class PostgreSQL extends SQL { // Connection Management public function connect() { - if(!is_null($this->connection)) { + if (!is_null($this->connection)) { return true; } @@ -59,7 +60,7 @@ class PostgreSQL extends SQL { ); $connectionString = array(); - foreach($config as $key => $val) { + foreach ($config as $key => $val) { if (!empty($val)) { $connectionString[] = "$key=$val"; } @@ -77,7 +78,7 @@ class PostgreSQL extends SQL { } public function disconnect() { - if(is_null($this->connection)) + if (is_null($this->connection)) return; @pg_close($this->connection); @@ -102,9 +103,9 @@ class PostgreSQL extends SQL { $pgParams = array(); if (!is_null($values)) { - foreach($values as $value) { + foreach ($values as $value) { $paramType = Parameter::parseType($value); - switch($paramType) { + switch ($paramType) { case Parameter::TYPE_DATE: $value = $value->format("Y-m-d"); break; @@ -154,35 +155,35 @@ class PostgreSQL extends SQL { } public function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string { - if (!is_null($strategy)) { - if ($strategy instanceof UpdateStrategy) { - $updateValues = array(); - foreach($strategy->getValues() as $key => $value) { - $leftColumn = $this->columnName($key); - if ($value instanceof Column) { - $columnName = $this->columnName($value->getName()); - $updateValues[] = "$leftColumn=EXCLUDED.$columnName"; - } else if ($value instanceof Add) { - $columnName = $this->columnName($value->getColumn()); - $operator = $value->getOperator(); - $value = $value->getValue(); - $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); - } else { - $updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters); - } + if (!is_null($strategy)) { + if ($strategy instanceof UpdateStrategy) { + $updateValues = array(); + foreach ($strategy->getValues() as $key => $value) { + $leftColumn = $this->columnName($key); + if ($value instanceof Column) { + $columnName = $this->columnName($value->getName()); + $updateValues[] = "$leftColumn=EXCLUDED.$columnName"; + } else if ($value instanceof Add) { + $columnName = $this->columnName($value->getColumn()); + $operator = $value->getOperator(); + $value = $value->getValue(); + $updateValues[] = "$leftColumn=$columnName$operator" . $this->addValue($value, $params); + } else { + $updateValues[] = "$leftColumn=" . $this->addValue($value, $parameters); } - - $conflictingColumns = $this->columnName($strategy->getConflictingColumns()); - $updateValues = implode(",", $updateValues); - return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues"; - } else { - $strategyClass = get_class($strategy); - $this->lastError = $this->logger->error("ON DUPLICATE Strategy $strategyClass is not supported yet."); - return null; } + + $conflictingColumns = $this->columnName($strategy->getConflictingColumns()); + $updateValues = implode(",", $updateValues); + return " ON CONFLICT ($conflictingColumns) DO UPDATE SET $updateValues"; } else { - return ""; + $strategyClass = get_class($strategy); + $this->lastError = $this->logger->error("ON DUPLICATE Strategy $strategyClass is not supported yet."); + return null; } + } else { + return ""; + } } public function getReturning(?string $columns): string { @@ -205,7 +206,7 @@ class PostgreSQL extends SQL { // UGLY but.. what should i do? private function createEnum(EnumColumn $enumColumn, string $typeName): string { $values = array(); - foreach($enumColumn->getValues() as $value) { + foreach ($enumColumn->getValues() as $value) { $values[] = $this->getValueDefinition($value); } @@ -228,22 +229,33 @@ class PostgreSQL extends SQL { } else { return "TEXT"; } - } else if($column instanceof SerialColumn) { + } else if ($column instanceof SerialColumn) { return "SERIAL"; - } else if($column instanceof IntColumn) { + } else if ($column instanceof IntColumn) { return $column->getType(); - } else if($column instanceof DateTimeColumn) { + } else if ($column instanceof DateTimeColumn) { return "TIMESTAMP"; - } else if($column instanceof EnumColumn) { + } else if ($column instanceof EnumColumn) { $typeName = $column->getName(); - if(!endsWith($typeName, "_type")) { + if (!endsWith($typeName, "_type")) { $typeName = "${typeName}_type"; } return $typeName; - } else if($column instanceof BoolColumn) { + } else if ($column instanceof BoolColumn) { return "BOOLEAN"; - } else if($column instanceof JsonColumn) { + } else if ($column instanceof JsonColumn) { 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 { $this->lastError = $this->logger->error("Unsupported Column Type: " . get_class($column)); return NULL; @@ -274,9 +286,9 @@ class PostgreSQL extends SQL { protected function getValueDefinition($value) { if (is_numeric($value)) { return $value; - } else if(is_bool($value)) { + } else if (is_bool($value)) { return $value ? "TRUE" : "FALSE"; - } else if(is_null($value)) { + } else if (is_null($value)) { return "NULL"; } else if ($value instanceof Keyword) { return $value->getValue(); @@ -312,7 +324,7 @@ class PostgreSQL extends SQL { public function tableName($table): string { if (is_array($table)) { $tables = array(); - foreach($table as $t) $tables[] = $this->tableName($t); + foreach ($table as $t) $tables[] = $this->tableName($t); return implode(",", $tables); } else { $parts = explode(" ", $table); @@ -328,15 +340,17 @@ class PostgreSQL extends SQL { public function columnName($col): string { if ($col instanceof KeyWord) { return $col->getValue(); - } elseif(is_array($col)) { - $columns = array_map(function ($c) { return $this->columnName($c); }, $col); + } elseif (is_array($col)) { + $columns = array_map(function ($c) { + return $this->columnName($c); + }, $col); return implode(",", $columns); } else { if (($index = strrpos($col, ".")) !== FALSE) { $tableName = $this->tableName(substr($col, 0, $index)); $columnName = $this->columnName(substr($col, $index + 1)); 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))); $alias = $this->columnName(trim(substr($col, $index + 4))); return "$columnName as $alias"; @@ -358,7 +372,7 @@ class PostgreSQL extends SQL { } public function buildCondition($condition, &$params) { - if($condition instanceof CondRegex) { + if ($condition instanceof CondRegex) { $left = $condition->getLeftExp(); $right = $condition->getRightExp(); $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) { $params = []; $query = "CREATE OR REPLACE FUNCTION $name() RETURNS TRIGGER AS \$table\$ BEGIN "; - foreach($statements as $stmt) { + foreach ($statements as $stmt) { if ($stmt instanceof Keyword) { $query .= $stmt->getValue() . ";"; } else { diff --git a/core/Driver/SQL/Query/CreateTable.class.php b/core/Driver/SQL/Query/CreateTable.class.php index a3c9e6d..37aab82 100644 --- a/core/Driver/SQL/Query/CreateTable.class.php +++ b/core/Driver/SQL/Query/CreateTable.class.php @@ -2,6 +2,11 @@ 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\StringColumn; use Driver\SQL\Column\IntColumn; @@ -10,6 +15,7 @@ use Driver\SQL\Column\EnumColumn; use Driver\SQL\Column\BoolColumn; use Driver\SQL\Column\JsonColumn; +use Driver\SQL\Constraint\Constraint; use Driver\SQL\Constraint\PrimaryKey; use Driver\SQL\Constraint\Unique; use Driver\SQL\Constraint\ForeignKey; @@ -31,6 +37,16 @@ class CreateTable extends Query { $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 { $this->columns[$name] = new SerialColumn($name); return $this; @@ -71,6 +87,21 @@ class CreateTable extends Query { 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 { $this->constraints[] = new PrimaryKey($names); return $this; diff --git a/core/Objects/DatabaseEntity/DatabaseEntity.class.php b/core/Objects/DatabaseEntity/DatabaseEntity.class.php new file mode 100644 index 0000000..25fccb8 --- /dev/null +++ b/core/Objects/DatabaseEntity/DatabaseEntity.class.php @@ -0,0 +1,75 @@ +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; + } +} \ No newline at end of file diff --git a/core/Objects/DatabaseEntity/DatabaseEntityHandler.php b/core/Objects/DatabaseEntity/DatabaseEntityHandler.php new file mode 100644 index 0000000..3138601 --- /dev/null +++ b/core/Objects/DatabaseEntity/DatabaseEntityHandler.php @@ -0,0 +1,188 @@ +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(); + } +} \ No newline at end of file diff --git a/core/core.php b/core/core.php index c8b0e76..bd6d6d3 100644 --- a/core/core.php +++ b/core/core.php @@ -1,5 +1,10 @@ ]~", "_", preg_replace("~[:\-]~", "", $sel)); -} - function html_attributes(array $attributes): string { return implode(" ", array_map(function ($key) use ($attributes) { $value = htmlspecialchars($attributes[$key]); diff --git a/test/DatabaseEntity.test.php b/test/DatabaseEntity.test.php new file mode 100644 index 0000000..e69de29