112 lines
3.3 KiB
PHP
112 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Core;
|
|
|
|
use PDO;
|
|
use PDOException;
|
|
|
|
/**
|
|
* PDO Database wrapper using Singleton pattern.
|
|
* Strict environment variable enforcement (No default fallbacks).
|
|
*/
|
|
class Database
|
|
{
|
|
private static ?PDO $instance = null;
|
|
|
|
/**
|
|
* Get active PDO database instance
|
|
*
|
|
* @return PDO
|
|
* @throws PDOException|\RuntimeException
|
|
*/
|
|
public static function getInstance(): PDO
|
|
{
|
|
return self::getConnection();
|
|
}
|
|
|
|
public static function getConnection(): PDO
|
|
{
|
|
if (self::$instance === null) {
|
|
$host = getenv('DB_HOST');
|
|
$port = getenv('DB_PORT');
|
|
$dbName = getenv('DB_DATABASE');
|
|
$username = getenv('DB_USERNAME');
|
|
$password = getenv('DB_PASSWORD');
|
|
|
|
$missing = [];
|
|
if ($host === false || $host === '') $missing[] = 'DB_HOST';
|
|
if ($port === false || $port === '') $missing[] = 'DB_PORT';
|
|
if ($dbName === false || $dbName === '') $missing[] = 'DB_DATABASE';
|
|
if ($username === false || $username === '') $missing[] = 'DB_USERNAME';
|
|
if ($password === false) $missing[] = 'DB_PASSWORD';
|
|
|
|
if (!empty($missing)) {
|
|
throw new \RuntimeException("Database Configuration Error: Missing environment variable(s): " . implode(', ', $missing));
|
|
}
|
|
|
|
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
|
|
|
|
$options = [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
];
|
|
|
|
try {
|
|
self::$instance = new PDO($dsn, $username, $password, $options);
|
|
} catch (PDOException $e) {
|
|
error_log("Database Connection Error: " . $e->getMessage());
|
|
throw new PDOException("Could not connect to MySQL database at {$host}:{$port}/{$dbName}. Error: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Shorthand execute statement with parameters
|
|
*/
|
|
public static function query(string $sql, array $params = []): \PDOStatement
|
|
{
|
|
$pdo = self::getConnection();
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
return $stmt;
|
|
}
|
|
|
|
/**
|
|
* Retrieve all matching records
|
|
*/
|
|
public static function select(string $sql, array $params = []): array
|
|
{
|
|
return self::query($sql, $params)->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Retrieve single matching record
|
|
*/
|
|
public static function selectOne(string $sql, array $params = [])
|
|
{
|
|
return self::query($sql, $params)->fetch() ?: null;
|
|
}
|
|
|
|
/**
|
|
* Insert record and return last inserted ID
|
|
*/
|
|
public static function insert(string $sql, array $params = []): string
|
|
{
|
|
$pdo = self::getConnection();
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
return $pdo->lastInsertId();
|
|
}
|
|
|
|
/**
|
|
* Execute generic non-query SQL (Update/Delete) and return affected rows
|
|
*/
|
|
public static function execute(string $sql, array $params = []): int
|
|
{
|
|
return self::query($sql, $params)->rowCount();
|
|
}
|
|
}
|