Database.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. namespace Libs;
  3. /**
  4. * Gerencia a conexão com o banco PostgreSQL.
  5. *
  6. * A aplicação roda como processo de longa duração (framework-X / ReactPHP),
  7. * portanto criar uma conexão PDO nova a cada chamada desperdiça recursos e
  8. * pode esgotar o pool de conexões do servidor. Aqui reutilizamos uma única
  9. * conexão persistente por processo (singleton), validando se ela continua
  10. * viva e reconectando quando necessário.
  11. */
  12. final class Database
  13. {
  14. private static ?\PDO $connection = null;
  15. public static function pdo(): \PDO
  16. {
  17. if (self::$connection instanceof \PDO && self::isAlive(self::$connection)) {
  18. return self::$connection;
  19. }
  20. self::$connection = self::connect();
  21. return self::$connection;
  22. }
  23. /**
  24. * Verifica se a conexão em cache ainda está utilizável.
  25. * Se o banco reiniciou ou derrubou a conexão, força a reconexão.
  26. */
  27. private static function isAlive(\PDO $pdo): bool
  28. {
  29. try {
  30. $pdo->query('SELECT 1');
  31. return true;
  32. } catch (\PDOException $e) {
  33. Logger::warning('Database connection lost, reconnecting', ['error' => $e->getMessage()]);
  34. return false;
  35. }
  36. }
  37. private static function connect(): \PDO
  38. {
  39. $host = self::env('DB_HOST', '127.0.0.1');
  40. $port = self::env('DB_PORT', '5432');
  41. $name = $_ENV['DB_NAME'] ?? '';
  42. $user = $_ENV['DB_USER'] ?? '';
  43. $pass = $_ENV['DB_PASS'] ?? '';
  44. if ($name === '') {
  45. Logger::error('DB_NAME is not configured');
  46. throw new \RuntimeException('DB_NAME is not configured.');
  47. }
  48. if (($_ENV['DB_USER'] ?? '') === '') {
  49. Logger::warning('DB_USER is empty; using empty database user');
  50. }
  51. $dsn = sprintf('pgsql:host=%s;port=%s;dbname=%s', $host, $port, $name);
  52. try {
  53. $pdo = new \PDO($dsn, $user, $pass, [
  54. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  55. \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
  56. \PDO::ATTR_PERSISTENT => true,
  57. ]);
  58. } catch (\PDOException $e) {
  59. Logger::error('Failed to connect to database', [
  60. 'host' => $host,
  61. 'port' => $port,
  62. 'name' => $name,
  63. 'error' => $e->getMessage(),
  64. ]);
  65. throw $e;
  66. }
  67. Logger::info('Database connection established', ['host' => $host, 'port' => $port, 'name' => $name]);
  68. return $pdo;
  69. }
  70. /**
  71. * Lê uma variável de ambiente registrando em log quando cai no valor default.
  72. * Torna visível o uso silencioso de defaults (ex.: host/porta ausentes).
  73. */
  74. private static function env(string $key, string $default): string
  75. {
  76. $value = $_ENV[$key] ?? null;
  77. if ($value === null || $value === '') {
  78. Logger::warning('Environment variable missing, using default', ['key' => $key, 'default' => $default]);
  79. return $default;
  80. }
  81. return (string) $value;
  82. }
  83. }