$host !== '' ? $host : 'localhost', 'DB_PORT' => $port !== '' ? $port : '3306', 'DB_NAME' => $name, 'DB_USER' => $user, 'DB_PASS' => $password, ]; } function db_export_settings_file(array $settings): string { $settings = db_normalize_settings($settings); $payload = " $settings['DB_HOST'], 'DB_PORT' => $settings['DB_PORT'], 'DB_NAME' => $settings['DB_NAME'], 'DB_USER' => $settings['DB_USER'], 'DB_PASS' => $settings['DB_PASS'], ], true) . "; "; return $payload; } function db_read_settings_file(string $path): array { static $cache = []; if (array_key_exists($path, $cache)) { return $cache[$path]; } $result = [ 'settings' => db_normalize_settings([]), 'error' => null, ]; if (!is_file($path)) { $cache[$path] = $result; return $result; } try { $loaded = include $path; } catch (Throwable $exception) { $result['error'] = $exception->getMessage(); $cache[$path] = $result; return $result; } if (!is_array($loaded)) { $result['error'] = 'Format file konfigurasi harus return array.'; $cache[$path] = $result; return $result; } $result['settings'] = db_normalize_settings($loaded); $cache[$path] = $result; return $result; } 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_manual_settings(): array { return db_read_settings_file(DB_MANUAL_CONFIG_FILE)['settings']; } function db_manual_settings_error(): ?string { return db_read_settings_file(DB_MANUAL_CONFIG_FILE)['error']; } function db_legacy_installer_settings(): array { return db_read_settings_file(DB_INSTALLER_CONFIG_FILE)['settings']; } function db_legacy_installer_settings_error(): ?string { return db_read_settings_file(DB_INSTALLER_CONFIG_FILE)['error']; } 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; } $manual = db_manual_settings(); if (db_settings_complete($manual)) { $manual['__source'] = 'manual_file'; $settings = $manual; return $settings; } $env = db_env_settings(); if (db_settings_complete($env)) { $env['__source'] = 'env'; $settings = $env; return $settings; } $legacy = db_legacy_installer_settings(); if (db_settings_complete($legacy)) { $legacy['__source'] = 'legacy_installer_file'; $settings = $legacy; 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_configuration_problem(): ?string { $manualError = db_manual_settings_error(); if ($manualError !== null) { return 'Ada error di file ' . db_manual_config_relative_path() . ': ' . $manualError; } $legacyError = db_legacy_installer_settings_error(); if ($legacyError !== null) { return 'Ada error di file lama db/installer-config.php: ' . $legacyError; } return null; } 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. Isi file ' . db_manual_config_relative_path() . '.'); } $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 = db_export_settings_file($settings); $bytes = file_put_contents(DB_MANUAL_CONFIG_FILE, $payload, LOCK_EX); if ($bytes === false) { throw new RuntimeException('Gagal menyimpan file konfigurasi database. Pastikan folder db bisa ditulis.'); } }