host = $config['host']; $this->user = $config['user']; $this->password = $config['password']; $this->database = $config['database']; if (isset($config['port']) && is_numeric($config['port'])) { $this->port = (int)$config['port']; } if (isset($config['ssl_mode']) && is_string($config['ssl_mode']) && $config['ssl_mode'] !== '') { $this->ssl_mode = $config['ssl_mode']; } } public static function getPDO(): \PDO { global $CONFIG_DB; $port = $CONFIG_DB['port'] ?? 3306; $dsn = "mysql:host={$CONFIG_DB['host']};port={$port};dbname={$CONFIG_DB['database']};charset=utf8mb4"; return new \PDO($dsn, $CONFIG_DB['user'], $CONFIG_DB['password'], [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, \PDO::ATTR_EMULATE_PREPARES => false, ]); } public function testConnection(): bool { try { $conn = new mysqli($this->host, $this->user, $this->password, $this->database, $this->port); if ($conn->connect_error) { return false; } $this->conn = $conn; return true; } catch (Exception) { return false; } } public function connect(): void { global $response; try { // Enable error reporting for mysqli to catch connection issues via exceptions mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $this->conn = new mysqli($this->host, $this->user, $this->password, $this->database, $this->port); if ($this->conn->connect_error) { throw new Exception($this->conn->connect_error); } } catch (Exception $e) { if ($response) { $response->internal_server_error("Database connection failed: " . $e->getMessage()); } else { throw $e; } } } public function escape_string(string $string): string { return $this->conn->real_escape_string($string); } public function close(): void { if (!isset($this->conn)) { return; } try { $this->conn->close(); } catch (\Throwable) { } } public function get(string $table, int $id) { $sql = "SELECT * FROM $table WHERE id = $id"; $result = $this->query($sql); return $this->fetch_assoc($result); } public function query(string $sql): \mysqli_result|bool { // If the connection is not established, connect return $this->conn->query($sql); } public function fetch_assoc($result) { return $result->fetch_assoc(); } public function list_objects(string $table): array { $sql = "SELECT * FROM $table"; $result = $this->query($sql); return $this->fetch_all($result); } public function fetch_all($result) { return $result->fetch_all(MYSQLI_ASSOC); } public function list_objects_paginated(string $table, int $page, int $limit): array { $offset = ($page - 1) * $limit; $sql = "SELECT * FROM $table LIMIT $limit OFFSET $offset"; $result = $this->query($sql); return $this->fetch_all($result); } public function count_objects(string $table): int { $sql = "SELECT COUNT(*) FROM $table"; $result = $this->query($sql); return $result->fetch_row()[0]; } public function insert_id(): int { return $this->conn->insert_id; } public function conn(): mysqli { return $this->conn; } public function prepare(string $sql): false|\mysqli_stmt { return $this->conn->prepare($sql); } public function num_rows(\mysqli_result|bool $result): int|string { return $result->num_rows; } public function getUsername() { return $this->user; } public function getPassword() { return $this->password; } public function getHost() { return $this->host; } public function getDatabase() { return $this->database; } public function getPort(): int { return $this->port; } public function getSslMode(): string { return $this->ssl_mode; } public function backupDatabase(string $path): bool { $mode = strtoupper(trim($this->ssl_mode)); $sslFlag = ''; switch ($mode) { case 'DISABLED': $sslFlag = '--skip-ssl'; break; case 'PREFERRED': $sslFlag = ''; break; case 'REQUIRED': case 'VERIFY_CA': case 'VERIFY_IDENTITY': default: $sslFlag = '--ssl'; break; } $host = escapeshellarg($this->host); $user = escapeshellarg($this->user); $db = escapeshellarg($this->database); $port = (int)$this->port; $sslPart = $sslFlag !== '' ? ($sslFlag . ' ') : ''; $command = "mysqldump {$sslPart}--single-transaction --quick --routines --triggers --events --hex-blob -h $host -P $port -u $user $db"; $directory = dirname($path); if (!is_dir($directory) && !mkdir($directory, 0770, true) && !is_dir($directory)) { return false; } $environment = array_merge(getenv() ?: [], $_ENV); $environment['MYSQL_PWD'] = $this->password; $descriptors = [ 0 => ['pipe', 'r'], 1 => ['file', $path, 'w'], 2 => ['pipe', 'w'], ]; $process = proc_open($command, $descriptors, $pipes, null, $environment); if (!is_resource($process)) { return false; } fclose($pipes[0]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); $return = proc_close($process); if ($return !== 0 && is_string($stderr) && $stderr !== '') { @file_put_contents($path . '.error.log', $stderr); } return $return === 0 && is_file($path) && filesize($path) !== false; } public function getView(string $view): array { $sql = "SELECT * FROM $view"; $result = $this->query($sql); return $this->fetch_all($result); } /** * Generate a fake ID for a table. * Fake ids are not stored in the database, they are generated on every request starting from -1. * This is useful for testing purposes. * @see orders_o::simulateOrderFromXLVask() * @param string $table * @return int */ public function getNextFakeId(string $table): int { // This is a static variable that will be shared across all instances of the class static $fakeId = 0; // Start from 0 and decrement on each call $fakeId--; return $fakeId; } }