parent = $parent; $this->id = $data["ID"]; $definitions = $parent->Definitions(); $this->fields = array_combine(array_keys($definitions["fields"]), array_column($definitions["fields"], "value")); $this->keys = array_combine(array_keys($definitions["keys"]), array_column($definitions["keys"], "reference")); $this->links = array_combine(array_keys($definitions["links"]), array_column($definitions["links"], "referenceList")); $this->Patch($data); } public function ID() {return $this->id;} public function Checksum() {return $this->chksum;} public function Patch($data) { // Overwrite field values if data exists foreach ($this->fields as $field => &$value) { if (isset($data[$field])) { $value = $data[$field]; } } unset($value); // If removed, $value would still point to last element of fields $definitions = $this->parent->Definitions(); foreach ($this->keys as $key => &$reference) { if (isset($data[$key])) { $class = "\\Resources\\".$definitions["keys"][$key]["resourceClass"]; $reference = is_array($data[$key]) ? $class::Get()->RefByData($data[$key]) : $class::Get()->RefById($data[$key]); } } unset($reference); // If removed, $reference would still point to last element of keys foreach ($this->links as $link => &$refList) { if (isset($data[$link])) { $class = "\\Links\\".$definitions["links"][$link]["linkClass"]; $refList = $class::Get()->RefList($this->id, $link, $data[$link]); } } unset($refList); // If removed, $reference would still point to last element of keys if (isset($data["CHKSUM"]) && !is_null($data["CHKSUM"])) { $this->chksum = $data["CHKSUM"]; } else { $this->updateChecksum(); } } public function Store() { $fields = []; $values = []; $types = ""; if ($this->parent->HasChksum()) { $fields[] = "CHKSUM"; $values[] = $this->chksum; $types.= "s"; } $definitions = $this->parent->Definitions(); foreach ($definitions["fields"] as $field => $def) { $fields[] = $field; $values[] = $this->fields[$field]; $types.= $def["type"]; } foreach ($definitions["keys"] as $key => $def) { $fields[] = $key; $values[] = is_null($this->keys[$key]) ? null : $this->keys[$key]->ID(); $types.= $def["type"]; } $qry = "UPDATE ".$this->parent->Table()." SET ".implode(" = ?, ", $fields)." = ? WHERE ID = ?"; $values[] = $this->id; $types.= "s"; if ($stmt = \DB::Get()->prepare($qry)) { $stmt->bind_param($types, ...$values); if ($stmt->execute()) { $this->parent->UpdateChecksum(); return true; } } \Response::Get()->DbError(); return false; } protected function updateChecksum() { $this->chksum = $this->parent->CalcChecksum($this->Json(0)); } public function Json($depth = null) { if (is_null($depth)) { $depth = \Request::DetailDepth(); } $ret = [ "ID" => $this->id, "CHKSUM" => $this->chksum ]; foreach ($this->fields as $field => $value) { $ret[$field] = $value; } foreach ($this->keys as $key => $reference) { $ret[$key] = is_null($reference) ? null : ($depth>0 ? $reference->Json($depth-1) : $reference->ID()); } foreach ($this->links as $link => $referenceList) { $ids = []; foreach ($referenceList as $reference) { if (!is_null($reference)) { $ids[] = $depth>0 ? $reference->Json($depth-1) : $reference->ID(); } } $ret[$link] = $ids; } return $ret; } } abstract class Handler { protected static $instances = []; protected $liste = []; public static function Get() { $class = get_called_class(); if (!isset($instances[$class])) { self::$instances[$class] = new $class(); } return self::$instances[$class]; } private function __construct() {} public function Table() {return $this->names["table"];} public function Ident() {return $this->names["ident"];} public function Short() {return $this->names["short"];} public function TableWithShort() {return $this->names["table"]." ".$this->names["short"];} public function HasUuid() {return $this->has["uuid"];} public function HasChksum() {return $this->has["sha256"];} public function Definitions() {return $this->definitions;} public function RefById($id) { if (!isset($this->liste[$id]) ) { $this->liste[$id] = $this->load($id); // Might be null, but we will buffer the result anyway } return $this->liste[$id]; } public function RefByData($data) { if (!isset($data["ID"])) { return null; } if (!isset($this->liste[$data["ID"]])) { $this->liste[$data["ID"]] = new Element($this, $data); } return $this->liste[$data["ID"]]; } public function RefAll() { $this->liste = $this->load(); return $this->liste; } protected function loadChecksum($id = null) { $field = "CHKSUM"; if (!$this->has["sha256"]) { // There is no checksum column to use. Smasch all columns together $fields = []; $arr = array_merge(array_keys($this->definitions["fields"]), array_keys($this->definitions["keys"])); $field = "CONCAT(IFNULL(".implode(", '__NULL__'), IFNULL(", $arr).", '__NULL__'))"; } if (is_null($id)) { // We need to concat all the rows $field = "GROUP_CONCAT(".$field.")"; } if (!$this->has["sha256"] || is_null($id)) { // Call the hash function, because $field is not a pregenerated checksum $field = "SHA2(".$field.", 256)"; } $qry = "SELECT ".$field." cs FROM ".$this->Table(); if (!is_null($id)) { $qry.= " WHERE ID = ?"; } if ($stmt = \DB::Get()->prepare($qry)) { if (!is_null($id)) { $stmt->bind_param("s", $id); } if ($stmt->execute()) { $chksum = $stmt->get_result()->fetch_assoc()["cs"]; return $chksum; } } \Response::Get()->DbError(); return null; } public function CalcChecksum($data) { $concat = ""; foreach ($this->definitions as $definition) { foreach ($definition as $field => $value) { $concat.= is_array($data[$field]) ? implode("", $data[$field]) : ($data[$field] ?? "__NULL__"); } } return hash("sha256", $concat); } public function Checksum($id = null) { if (is_null($id)) { return \KV::Get()->get("Routes:".get_called_class().":Checksum"); } else if (isset($this->liste[$id])) { return $this->liste[$id]->Checksum(); } else { return $this->loadChecksum($id); } } public function UpdateChecksum() { $checksum = $this->loadChecksum(); \KV::Get()->set("Routes:".get_called_class().":Checksum", $checksum); } public function Remove($id) { if ($stmt = \DB::Get()->prepare("DELETE FROM ".$this->Table()." WHERE ID = ?")) { $stmt->bind_param("s", $id); if ($stmt->execute()) { if (1==$stmt->affected_rows) { $this->UpdateChecksum(); return true; } else if (0==$stmt->affected_rows) { \Response::Get()->Message("Fehler: Es wurde nichts entfernt!"); } else { \Response::Get()->Message("Fehler: Es wurden mehrere Einträge entfernt!"); } } } \Response::Get()->DbError(); return false; } public function Insert($data, &$newid) { $fields = []; $values = []; $types = ""; $placeholders = []; if ($this->has["uuid"]) { $res = \DB::Get()->query("SELECT UUID_SHORT() uuid"); $fields[] = "ID"; $values[] = $res->fetch_assoc()["uuid"]; $types.= "s"; $placeholders[] = "?"; } if ($this->has["sha256"]) { $fields[] = "CHKSUM"; $values[] = $this->CalcChecksum($data); $types.= "s"; $placeholders[] = "?"; } foreach ($this->definitions["fields"] as $field => $def) { if (isset($data[$field])) { $fields[] = $field; $values[] = $data[$field]; $types.= $def["type"]; $placeholders[] = "?"; } } foreach ($this->definitions["keys"] as $key => $def) { if (isset($data[$key])) { $fields[] = $key; $values[] = $data[$key]=="__NULL__" ? null : $data[$key]; $types.= $def["type"]; $placeholders[] = "?"; } } $qry = "INSERT INTO ".$this->Table()." (".implode(", ", $fields).") VALUES (".implode(", ", $placeholders).")"; if ($stmt = \DB::Get()->prepare($qry)) { $stmt->bind_param($types, ...$values); if ($stmt->execute()) { $newid = $this->HasUuid() ? $values[0] : \DB::Get()->insert_id; $this->UpdateChecksum(); return true; } } \Response::Get()->DbError(); return false; } protected function load($id = null) { $fields = [$this->Short().".*"]; $joins = []; foreach ($this->definitions["keys"] as $key => $definition) { $class = "\\Resources\\".$definition["resourceClass"]; $joins[] = \DB::LeftJoin($class::Get()->Table(), $class::Get()->Short(), "ID", $this->Short(), $key); $fields[] = $key; } foreach ($this->definitions["links"] as $link => $definition) { $resClass = "\\Resources\\".$definition["resourceClass"]; $lnkClass = "\\Links\\".$definition["linkClass"]; $joins[] = \DB::LeftJoin($lnkClass::Get()->Table(), $lnkClass::Get()->Short(), $this->Ident(), $this->Short(), "ID"); //$joins[] = \DB::LeftJoin($resClass::Get()->Table(), $resClass::Get()->Short(), "ID", $lnkClass::Get()->Short(), $link); $fields[] = "CONCAT(',', GROUP_CONCAT(DISTINCT ".$lnkClass::Get()->Short().".".$link." SEPARATOR ','), ',') ".$link; } $qry = "SELECT ".implode(", ", $fields)." FROM ".$this->TableWithShort()." ".implode(" ", $joins); if (!is_null($id)) { $qry.= "WHERE ".$this->Short().".ID = ?"; } if ($stmt = \DB::Get()->prepare($qry)) { if (!is_null($id)) { $stmt->bind_param("s", $id); } if ($stmt->execute()) { $res = $stmt->get_result(); if (is_null($id)) { $ret = []; while ($row = $res->fetch_assoc()) { $ret[] = new Element($this, $row); } return $ret; } else { if ($row = $res->fetch_assoc()) { return new Element($this, $row); } return null; } } } \Response::Get()->DbError(); return null; } }