$value) { if (property_exists(get_called_class(), $field)) { $this->$field = $value; } } } protected static function getFullyQualifiedTable(): string { $table = str_replace('Model', '', get_called_class()); $tableIncludesADBSuffix = strpos($table, 'ADB') !== false; $table = str_replace('ADB', '', $table); return "`" . ($tableIncludesADBSuffix ? ADDRESSDB_DBNAME : FRONKDB_DBNAME) . "`.`" . $table . "`"; } protected static function getDB() { if (strpos(self::getFullyQualifiedTable(), ADDRESSDB_DBNAME) !== false) $FronkDB = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); else $FronkDB = FronkDB::singleton(); return $FronkDB->link; } public static function create($data) { $db = self::getDB(); $table = self::getFullyQualifiedTable(); self::checkAllFields($data, ['id']); $sqlColumns = []; $sqlValues = []; foreach ($data as $field => $value) { if (!property_exists(get_called_class(), $field)) { die(json_encode([ "status" => "error", "error" => "Field $field does not exist in " . get_called_class(), "data" => $data ])); } if (is_array($value)) { $value = json_encode($value); } else if ($value === "" && (new ReflectionProperty(get_called_class(), $field))->getType()->getName() === "float") { $value = null; } else if ($value === "" && (new ReflectionProperty(get_called_class(), $field))->getType()->getName() === "int") { $value = null; } $sqlValues[] = $value === null ? 'NULL' : "'" . $db->real_escape_string($value) . "'"; $sqlColumns[] = "`$field`"; } $sql = "INSERT INTO $table (" . implode(", ", $sqlColumns) . ") VALUES (" . implode(", ", $sqlValues) . ")"; $db->query($sql) or die($db->error); return $db->insert_id; } /** * Checks if all required fields of the current class are present in a given data array. * * This method uses reflection to determine which fields are required based on their type declarations. * It then iterates over these required fields and throws an exception if any of them are missing * from the provided data array. * * @param array $data The data array to check. * @param array $skip An optional array of field names to skip during the check. * * @return void * @throws Exception If any required field is missing in the `$data` array. * */ public static function checkAllFields(array $data, array $skip = []) { $requiredVars = array_filter(get_class_vars(get_called_class()), function ($value, $key) { $reflectionProperty = new ReflectionProperty(get_called_class(), $key); return !$reflectionProperty->hasType() || !$reflectionProperty->getType()->allowsNull(); }, ARRAY_FILTER_USE_BOTH); foreach ($requiredVars as $field => $value) { if (in_array($field, $skip)) { continue; } if (!isset($data[$field])) { http_response_code(500); die(json_encode([ "status" => "error", "error" => "Required field $field is missing in data array", "data" => $data ])); } } } public static function get($id): TTCrudBaseModel { $db = self::getDB(); $id = $db->real_escape_string($id); $table = self::getFullyQualifiedTable(); $sql = "SELECT * FROM $table WHERE `id` = $id"; $result = $db->query($sql); // as TTCRudBaseModel is abstract, we need to get the class name of the child class $class = get_called_class(); return new $class($result->fetch_assoc()); } public static function count($filter = []): int { $db = self::getDB(); $table = self::getFullyQualifiedTable(); $filter = self::getSQLFilter($filter); $sql = "SELECT COUNT(*) as count FROM $table $filter"; $result = $db->query($sql); return $result->fetch_assoc()['count']; } public static function getSQLFilter(array $filter): string { if (empty($filter)) return ''; $sql = 'WHERE 1=1'; $calledClass = get_called_class(); foreach ($filter as $key => $value) { foreach (explode('|', $key) as $column) { if (!property_exists($calledClass, $column)) { throw new InvalidArgumentException("Field $column does not exist in $calledClass"); } } $sql .= Helper::generateFilterCondition($value, $key, gettype($value) === "integer"); } return $sql; } public static function getAll($filter = [], $limit = null, $offset = 0, $order = ["key" => null]): array { $db = self::getDB(); $table = self::getFullyQualifiedTable(); $filter = self::getSQLFilter($filter); $sql = "SELECT * FROM $table $filter"; $sql .= $order['key'] === null ? " ORDER BY `id` ASC" : " ORDER BY `" . $order['key'] . "` " . $order['order']; $sql .= $limit === null ? "" : " LIMIT " . $limit . " OFFSET " . $offset; try { $result = $db->query($sql); } catch (Exception $e) { echo $sql; die($e->getMessage()); } $rows = []; $class = get_called_class(); while ($row = $result->fetch_assoc()) { $rows[] = new $class($row); } return $rows; } public static function update($data) { $db = self::getDB(); $table = self::getFullyQualifiedTable(); // Check if all fields are set self::checkAllFields($data); $values = []; foreach ($data as $field => $value) { if (!property_exists(get_called_class(), $field)) { throw new Exception("Field $field does not exist in " . get_called_class()); } if ($field === "id") { continue; } // TODO: make this cleaner if ($value === "" && (new ReflectionProperty(get_called_class(), $field))->getType()->getName() === "float") { $value = null; } if ($value === "" && (new ReflectionProperty(get_called_class(), $field))->getType()->getName() === "int") { $value = null; } if (is_array($value)) { $value = json_encode($value); } $values[] = $value === null ? "`$field` = NULL" : "`$field` = '" . $db->real_escape_string($value) . "'"; } $sql = "UPDATE $table SET " . implode(", ", $values) . " WHERE `id` = " . $db->real_escape_string($data['id']); $db->query($sql); return $db->affected_rows; } public static function delete($id) { $db = self::getDB(); $table = self::getFullyQualifiedTable(); $id = $db->real_escape_string($id); $sql = "DELETE FROM $table WHERE `id` = $id"; $db->query($sql); return $db->affected_rows; } public static function getFirst($filter = [], $order = ["key" => null]): ?TTCrudBaseModel { $db = self::getDB(); $table = self::getFullyQualifiedTable(); $filter = self::getSQLFilter($filter); $sql = "SELECT * FROM $table $filter"; if ($order['key'] !== null) { $sql .= " ORDER BY `" . $order['key'] . "` " . $order['order']; } else { $sql .= " ORDER BY `id` ASC"; } $sql .= " LIMIT 1"; $result = $db->query($sql); if ($result->num_rows === 0) { return null; } return new static($result->fetch_assoc()); } }