40024-vm/db/config.php
2026-05-17 15:57:40 +00:00

248 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
const DB_INSTALLER_CONFIG_FILE = __DIR__ . '/installer-config.php';
function db_normalize_settings(array $settings): array
{
$host = trim((string)($settings['DB_HOST'] ?? ''));
$port = trim((string)($settings['DB_PORT'] ?? ''));
$name = trim((string)($settings['DB_NAME'] ?? ''));
$user = trim((string)($settings['DB_USER'] ?? ''));
$password = isset($settings['DB_PASS']) ? (string)$settings['DB_PASS'] : '';
return [
'DB_HOST' => $host !== '' ? $host : 'localhost',
'DB_PORT' => $port !== '' ? $port : '3306',
'DB_NAME' => $name,
'DB_USER' => $user,
'DB_PASS' => $password,
];
}
function db_env_settings(): array
{
$settings = [];
foreach (['DB_HOST', 'DB_PORT', 'DB_NAME', 'DB_USER', 'DB_PASS'] as $key) {
$value = getenv($key);
if ($value !== false) {
$settings[$key] = $value;
}
}
return db_normalize_settings($settings);
}
function db_local_settings(): array
{
static $settings = null;
if (is_array($settings)) {
return $settings;
}
if (!is_file(DB_INSTALLER_CONFIG_FILE)) {
$settings = db_normalize_settings([]);
return $settings;
}
$loaded = include DB_INSTALLER_CONFIG_FILE;
$settings = is_array($loaded) ? db_normalize_settings($loaded) : db_normalize_settings([]);
return $settings;
}
function db_settings_complete(array $settings): bool
{
return $settings['DB_HOST'] !== ''
&& $settings['DB_NAME'] !== ''
&& $settings['DB_USER'] !== '';
}
function db_resolved_settings(): array
{
static $settings = null;
if (is_array($settings)) {
return $settings;
}
$env = db_env_settings();
$local = db_local_settings();
$envComplete = db_settings_complete($env);
$localComplete = db_settings_complete($local);
if ($envComplete && $localComplete) {
$error = null;
if (db_test_connection($env, true, $error)) {
$env['__source'] = 'env';
$settings = $env;
return $settings;
}
$local['__source'] = 'file';
$settings = $local;
return $settings;
}
if ($envComplete) {
$env['__source'] = 'env';
$settings = $env;
return $settings;
}
if ($localComplete) {
$local['__source'] = 'file';
$settings = $local;
return $settings;
}
$settings = db_normalize_settings([]);
$settings['__source'] = 'missing';
return $settings;
}
$resolvedDbSettings = db_resolved_settings();
define('DB_HOST', $resolvedDbSettings['DB_HOST']);
define('DB_PORT', $resolvedDbSettings['DB_PORT']);
define('DB_NAME', $resolvedDbSettings['DB_NAME']);
define('DB_USER', $resolvedDbSettings['DB_USER']);
define('DB_PASS', $resolvedDbSettings['DB_PASS']);
unset($resolvedDbSettings);
function db_config_source(): string
{
return db_resolved_settings()['__source'] ?? 'missing';
}
function db_has_required_config(): bool
{
return db_settings_complete(db_resolved_settings());
}
function db_build_dsn(array $settings, bool $withDatabase = true): string
{
$settings = db_normalize_settings($settings);
$dsn = 'mysql:host=' . $settings['DB_HOST'] . ';port=' . $settings['DB_PORT'] . ';charset=utf8mb4';
if ($withDatabase && $settings['DB_NAME'] !== '') {
$dsn .= ';dbname=' . $settings['DB_NAME'];
}
return $dsn;
}
function db_create_pdo(array $settings, bool $withDatabase = true): PDO
{
$settings = db_normalize_settings($settings);
return new PDO(
db_build_dsn($settings, $withDatabase),
$settings['DB_USER'],
$settings['DB_PASS'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_TIMEOUT => 5,
]
);
}
function db_test_connection(array $settings, bool $withDatabase = true, ?string &$error = null): bool
{
try {
db_create_pdo($settings, $withDatabase)->query('SELECT 1');
$error = null;
return true;
} catch (Throwable $exception) {
$error = $exception->getMessage();
return false;
}
}
function db_connection_error(?array $settings = null): ?string
{
$settings = $settings !== null ? db_normalize_settings($settings) : db_resolved_settings();
if (!db_settings_complete($settings)) {
return 'Database belum dikonfigurasi lengkap.';
}
$error = null;
db_test_connection($settings, true, $error);
return $error;
}
function db(): PDO
{
static $pdo = null;
if ($pdo instanceof PDO) {
return $pdo;
}
if (!db_has_required_config()) {
throw new RuntimeException('Database belum dikonfigurasi.');
}
$pdo = db_create_pdo(db_resolved_settings(), true);
return $pdo;
}
function db_error_is_missing_database(string $message): bool
{
return stripos($message, 'Unknown database') !== false
|| str_contains($message, '[1049]');
}
function db_quote_identifier(string $identifier): string
{
$identifier = trim($identifier);
if ($identifier === '' || preg_match('/[[:cntrl:]]/', $identifier)) {
throw new RuntimeException('Nama database tidak valid.');
}
return '`' . str_replace('`', '``', $identifier) . '`';
}
function db_create_database_if_missing(array $settings): void
{
$settings = db_normalize_settings($settings);
if ($settings['DB_NAME'] === '') {
throw new RuntimeException('Nama database wajib diisi.');
}
$pdo = db_create_pdo($settings, false);
$pdo->exec(
'CREATE DATABASE IF NOT EXISTS ' . db_quote_identifier($settings['DB_NAME']) .
' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'
);
}
function db_save_local_settings(array $settings): void
{
$settings = db_normalize_settings($settings);
$payload = "<?php
";
$payload .= "declare(strict_types=1);
";
$payload .= 'return ' . var_export([
'DB_HOST' => $settings['DB_HOST'],
'DB_PORT' => $settings['DB_PORT'],
'DB_NAME' => $settings['DB_NAME'],
'DB_USER' => $settings['DB_USER'],
'DB_PASS' => $settings['DB_PASS'],
], true) . ";
";
$bytes = file_put_contents(DB_INSTALLER_CONFIG_FILE, $payload, LOCK_EX);
if ($bytes === false) {
throw new RuntimeException('Gagal menyimpan file konfigurasi database. Pastikan folder db bisa ditulis.');
}
}