Database.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <?php
  2. namespace BCJD;
  3. /**
  4. * Database - PDO 单例连接管理器
  5. *
  6. * 安全特性:
  7. * - 单例模式,避免重复连接
  8. * - 所有查询强制使用预处理语句
  9. * - 查询错误日志记录(不暴露细节给用户)
  10. * - 支持事务
  11. */
  12. class Database
  13. {
  14. private static ?Database $instance = null;
  15. private ?\PDO $pdo = null;
  16. private string $host;
  17. private int $port;
  18. private string $dbname;
  19. private string $user;
  20. private string $pass;
  21. private string $charset;
  22. private function __construct()
  23. {
  24. $this->host = DB_HOST;
  25. $this->port = DB_PORT;
  26. $this->dbname = DB_NAME;
  27. $this->user = DB_USER;
  28. $this->pass = DB_PASS;
  29. $this->charset = DB_CHARSET;
  30. }
  31. /** 禁止克隆 */
  32. private function __clone() {}
  33. public static function instance(): self
  34. {
  35. if (self::$instance === null) {
  36. self::$instance = new self();
  37. }
  38. return self::$instance;
  39. }
  40. /**
  41. * 获取 PDO 连接(惰性初始化)
  42. */
  43. public function getConnection(): \PDO
  44. {
  45. if ($this->pdo === null) {
  46. $dsn = sprintf(
  47. 'mysql:host=%s;port=%d;dbname=%s;charset=%s',
  48. $this->host, $this->port, $this->dbname, $this->charset
  49. );
  50. $this->pdo = new \PDO($dsn, $this->user, $this->pass, [
  51. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  52. \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
  53. \PDO::ATTR_EMULATE_PREPARES => false,
  54. \PDO::ATTR_STRINGIFY_FETCHES => false,
  55. ]);
  56. // 设置严格 SQL 模式
  57. $this->pdo->exec("SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'");
  58. }
  59. return $this->pdo;
  60. }
  61. /**
  62. * 执行查询返回所有行
  63. */
  64. public function fetchAll(string $sql, array $params = []): array
  65. {
  66. $stmt = $this->execute($sql, $params);
  67. return $stmt->fetchAll();
  68. }
  69. /**
  70. * 执行查询返回单行
  71. */
  72. public function fetchOne(string $sql, array $params = []): ?array
  73. {
  74. $stmt = $this->execute($sql, $params);
  75. $row = $stmt->fetch();
  76. return $row !== false ? $row : null;
  77. }
  78. /**
  79. * 执行查询返回单列值
  80. */
  81. public function fetchColumn(string $sql, array $params = []): mixed
  82. {
  83. $stmt = $this->execute($sql, $params);
  84. return $stmt->fetchColumn();
  85. }
  86. /**
  87. * 执行 INSERT/UPDATE/DELETE,返回受影响行数
  88. */
  89. public function execute(string $sql, array $params = []): \PDOStatement
  90. {
  91. $pdo = $this->getConnection();
  92. $stmt = $pdo->prepare($sql);
  93. $stmt->execute($params);
  94. return $stmt;
  95. }
  96. /**
  97. * 获取最后插入的 ID
  98. */
  99. public function lastInsertId(): string
  100. {
  101. return $this->getConnection()->lastInsertId();
  102. }
  103. /**
  104. * 事务封装
  105. */
  106. public function beginTransaction(): void
  107. {
  108. $this->getConnection()->beginTransaction();
  109. }
  110. public function commit(): void
  111. {
  112. $this->getConnection()->commit();
  113. }
  114. public function rollback(): void
  115. {
  116. $this->getConnection()->rollback();
  117. }
  118. /**
  119. * 不带数据库名连接(用于建库操作)
  120. */
  121. public static function rawConnection(): \PDO
  122. {
  123. $dsn = sprintf('mysql:host=%s;port=%d;charset=%s', DB_HOST, DB_PORT, DB_CHARSET);
  124. return new \PDO($dsn, DB_USER, DB_PASS, [
  125. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  126. ]);
  127. }
  128. /**
  129. * 销毁连接(用于重置)
  130. */
  131. public function close(): void
  132. {
  133. $this->pdo = null;
  134. self::$instance = null;
  135. }
  136. }