connection = new PDO("mysql:host=$host;dbname=$name;charset=utf8mb4", $user, $password, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); } // Give up if we have a connection error. catch (PDOException $e) { http_response_code(503); echo '

Database Connection Problems

Our software could not connect to the database. We apologise for any inconvenience and ask you to check back later.

'; exit; } } public function getQueryCount() { return $this->query_count; } public function getLoggedQueries() { return $this->logged_queries; } /** * Fetches a row from a given statement/recordset, using field names as keys. */ public function fetchAssoc($stmt) { return $stmt->fetch(PDO::FETCH_ASSOC); } /** * Fetches a row from a given statement/recordset, encapsulating into an object. */ public function fetchObject($stmt, $class) { return $stmt->fetchObject($class); } /** * Fetches a row from a given statement/recordset, using numeric keys. */ public function fetchNum($stmt) { return $stmt->fetch(PDO::FETCH_NUM); } /** * Destroys a given statement/recordset. */ public function free($stmt) { return $stmt->closeCursor(); } /** * Returns the amount of rows in a given statement/recordset. */ public function rowCount($stmt) { return $stmt->rowCount(); } /** * Returns the amount of fields in a given statement/recordset. */ public function columnCount($stmt) { return $stmt->columnCount(); } /** * Returns the id of the row created by a previous query. */ public function insertId($name = null) { return $this->connection->lastInsertId($name); } /** * Start a transaction. */ public function beginTransaction() { return $this->connection->beginTransaction(); } /** * Rollback changes in a transaction. */ public function rollback() { return $this->connection->rollBack(); } /** * Commit changes in a transaction. */ public function commit() { return $this->connection->commit(); } private function expandPlaceholders($db_string, array &$db_values) { foreach ($db_values as $key => &$value) { if (str_contains($db_string, ':' . $key)) { if (is_array($value)) { throw new UnexpectedValueException('Array ' . $key . ' is used as a scalar placeholder. Did you mean to use \'@\' instead?'); } // Prepare date/time values if (is_a($value, 'DateTime')) { $value = $value->format('Y-m-d H:i:s'); } } elseif (str_contains($db_string, '@' . $key)) { if (!is_array($value)) { throw new UnexpectedValueException('Scalar value ' . $key . ' is used as an array placeholder. Did you mean to use \':\' instead?'); } // Create placeholders for all array elements $placeholders = array_map(fn($num) => ':' . $key . $num, range(0, count($value) - 1)); $db_string = str_replace('@' . $key, implode(', ', $placeholders), $db_string); } else { // throw new Exception('Warning: unused key in query: ' . $key); } } return $db_string; } /** * Escapes and quotes a string using values passed, and executes the query. */ public function query($db_string, array $db_values = []): PDOStatement { // One more query... $this->query_count++; // Error out if hardcoded strings are detected if (strpos($db_string, '\'') !== false) throw new UnexpectedValueException('Hack attempt: illegal character (\') used in query.'); if (defined('DB_LOG_QUERIES') && DB_LOG_QUERIES) $this->logged_queries[] = $db_string; try { // Preprocessing/checks: prepare any arrays for binding $db_string = $this->expandPlaceholders($db_string, $db_values); // Prepare query for execution $statement = $this->connection->prepare($db_string); // Bind parameters... the hard way, due to a limit/offset hack. // NB: bindParam binds by reference, hence &$value here. foreach ($db_values as $key => &$value) { // Assumption: both scalar and array values are preprocessed to use named ':' placeholders if (!str_contains($db_string, ':' . $key)) continue; if (!is_array($value)) { $statement->bindParam(':' . $key, $value); continue; } foreach (array_values($value) as $num => &$element) { $statement->bindParam(':' . $key . $num, $element); } } $statement->execute(); return $statement; } catch (PDOException $e) { ob_start(); $debug = ob_get_clean(); throw new Exception($e->getMessage() . "\n" . var_export($e->errorInfo, true) . "\n" . var_export($db_values, true)); } } /** * Executes a query, returning an object of the row it returns. */ public function queryObject($class, $db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->rowCount($res) === 0) return null; $object = $this->fetchObject($res, $class); $this->free($res); return $object; } /** * Executes a query, returning an array of objects of all the rows returns. */ public function queryObjects($class, $db_string, $db_values = []) { $res = $this->query($db_string, $db_values); if (!$res || $this->rowCount($res) === 0) return []; $rows = []; while ($object = $this->fetchObject($res, $class)) $rows[] = $object; $this->free($res); return $rows; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryRow($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); if ($this->rowCount($res) === 0) return []; $row = $this->fetchNum($res); $this->free($res); return $row; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryRows($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); if ($this->rowCount($res) === 0) return []; $rows = []; while ($row = $this->fetchNum($res)) $rows[] = $row; $this->free($res); return $rows; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryPair($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); if ($this->rowCount($res) === 0) return []; $rows = []; while ($row = $this->fetchNum($res)) $rows[$row[0]] = $row[1]; $this->free($res); return $rows; } /** * Executes a query, returning an array of all the rows it returns. */ public function queryPairs($db_string, $db_values = array()) { $res = $this->query($db_string, $db_values); if (!$res || $this->rowCount($res) === 0) return []; $rows = []; while ($row = $this->fetchAssoc($res)) { $key_value = reset($row); $rows[$key_value] = $row; } $this->free($res); return $rows; } /** * Executes a query, returning an associative array of all the rows it returns. */ public function queryAssoc($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); if ($this->rowCount($res) === 0) return []; $row = $this->fetchAssoc($res); $this->free($res); return $row; } /** * Executes a query, returning an associative array of all the rows it returns. */ public function queryAssocs($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); if ($this->rowCount($res) === 0) return []; $rows = []; while ($row = $this->fetchAssoc($res)) $rows[] = $row; $this->free($res); return $rows; } /** * Executes a query, returning the first value of the first row. */ public function queryValue($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); // If this happens, you're doing it wrong. if ($this->rowCount($res) === 0) return null; list($value) = $this->fetchNum($res); $this->free($res); return $value; } /** * Executes a query, returning an array of the first value of each row. */ public function queryValues($db_string, array $db_values = []) { $res = $this->query($db_string, $db_values); if ($this->rowCount($res) === 0) return []; $rows = []; while ($row = $this->fetchNum($res)) $rows[] = $row[0]; $this->free($res); return $rows; } /** * This function can be used to insert data into the database in a secure way. */ public function insert($method, $table, $columns, $data) { // With nothing to insert, simply return. if (empty($data)) return; // Inserting data as a single row can be done as a single array. if (!is_array($data[array_rand($data)])) $data = [$data]; // Determine the method of insertion. $method = $method == 'replace' ? 'REPLACE' : ($method == 'ignore' ? 'INSERT IGNORE' : 'INSERT'); // What columns are we inserting? $columns = array_keys($data[0]); // Start building the query. $db_string = $method . ' INTO ' . $table . ' (' . implode(',', $columns) . ') VALUES '; // Create the mold for a single row insert. $placeholders = '(' . substr(str_repeat('?, ', count($columns)), 0, -2) . '), '; // Append it for every row we're to insert. $values = []; foreach ($data as $row) { $values = array_merge($values, array_values($row)); $db_string .= $placeholders; } // Get rid of the tailing comma. $db_string = substr($db_string, 0, -2); // Prepare for your impending demise! $statement = $this->connection->prepare($db_string); // Bind parameters... the hard way, due to a limit/offset hack. foreach ($values as $key => $value) $statement->bindValue($key + 1, $values[$key]); // Handle errors. try { $statement->execute(); return $statement; } catch (PDOException $e) { throw new Exception($e->getMessage() . '

' . $db_string . '

' . print_r($values, true)); } } }