db.php 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. <?php
  2. // db.php – shared bootstrap: error handling + PDO helper.
  3. // Included by every script that needs a database connection, so the error
  4. // configuration below applies site-wide.
  5. require_once __DIR__ . '/config.php';
  6. // $appEnv comes from config.php. Anything other than an explicit
  7. // 'development' is treated as production, so a missing or misspelt value
  8. // fails closed and never leaks internals to visitors.
  9. define('APP_DEBUG', (isset($appEnv) ? $appEnv : 'production') === 'development');
  10. error_reporting(E_ALL);
  11. ini_set('display_errors', APP_DEBUG ? '1' : '0');
  12. ini_set('display_startup_errors', APP_DEBUG ? '1' : '0');
  13. ini_set('log_errors', '1');
  14. /**
  15. * Log the real reason, show the visitor something harmless, stop.
  16. * Detail only reaches the browser in development.
  17. */
  18. function fail_hard(string $logMessage): void {
  19. error_log('PackIt: ' . $logMessage);
  20. if (!headers_sent()) {
  21. http_response_code(500);
  22. header('Content-Type: text/html; charset=utf-8');
  23. }
  24. if (APP_DEBUG) {
  25. echo '<p style="font-family:monospace;color:#c00">'
  26. . htmlspecialchars($logMessage) . '</p>';
  27. } else {
  28. echo '<p style="font-family:system-ui,sans-serif">Something went wrong on our side. '
  29. . 'Please try again in a moment.</p>';
  30. }
  31. exit;
  32. }
  33. // Uncaught exceptions (a dropped connection mid-query, a constraint we did not
  34. // anticipate) would otherwise print file paths and SQL to the page.
  35. set_exception_handler(function (Throwable $e): void {
  36. fail_hard(get_class($e) . ': ' . $e->getMessage()
  37. . ' in ' . $e->getFile() . ':' . $e->getLine());
  38. });
  39. /**
  40. * True when the visitor reached us over TLS, including through a reverse proxy
  41. * that terminates it and forwards X-Forwarded-Proto.
  42. */
  43. function request_is_https(): bool {
  44. if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') {
  45. return true;
  46. }
  47. if (strtolower($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') {
  48. return true;
  49. }
  50. return (int)($_SERVER['SERVER_PORT'] ?? 0) === 443;
  51. }
  52. function get_pdo(): PDO {
  53. global $host, $dbname, $username, $password, $charset;
  54. static $pdo = null;
  55. if ($pdo === null) {
  56. $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
  57. $options = [
  58. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  59. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  60. PDO::ATTR_EMULATE_PREPARES => false,
  61. ];
  62. try {
  63. $pdo = new PDO($dsn, $username, $password, $options);
  64. } catch (PDOException $e) {
  65. // The message names the host, database and user – log only.
  66. fail_hard('Database connection failed: ' . $e->getMessage());
  67. }
  68. }
  69. return $pdo;
  70. }