將重複的資料庫連線、查詢、關閉邏輯封裝為輔助類別,能減少樣板碼並統一錯誤處理。本文提供一個基於 MySQLi (物件導向) 的 DBHelper 類別範例,支援預處理語句、交易控制與常用查詢方法。注意: 原文使用已移除的 mysql_* 函式,此處已現代化重寫。
<?php
class DBHelper {
private $mysqli;
private $host, $user, $pass, $dbname, $charset;
public function __construct(array $config) {
$this->host = $config['host'] ?? 'localhost';
$this->user = $config['user'] ?? 'root';
$this->pass = $config['pass'] ?? '';
$this->dbname = $config['dbname'] ?? '';
$this->charset = $config['charset'] ?? 'utf8mb4';
}
// 延遲連線:首次用到時才連線
private function connect() {
if ($this->mysqli instanceof mysqli) return;
$this->mysqli = new mysqli($this->host, $this->user, $this->pass, $this->dbname);
if ($this->mysqli->connect_errno) {
throw new Exception("資料庫連線失敗: " . $this->mysqli->connect_error);
}
$this->mysqli->set_charset($this->charset);
}
// 執行預處理語句 (INSERT, UPDATE, DELETE, REPLACE)
// 回傳影響行數 (int) 或 false
public function execute(string $sql, array $params = [], array $types = []): int|false {
$this->connect();
$stmt = $this->mysqli->prepare($sql);
if (!$stmt) throw new Exception("Prepare 失敗: " . $this->mysqli->error . " [SQL: $sql]");
if ($params) {
$typeStr = $types ? implode('', $types) : str_repeat('s', count($params)); // 預設皆為字串
$stmt->bind_param($typeStr, ...$params);
}
$ok = $stmt->execute();
$affected = $stmt->affected_rows;
$stmt->close();
return $ok ? $affected : false;
}
// 查詢單筆資料 (關聯陣列)
public function queryOne(string $sql, array $params = [], array $types = []): array|false {
$this->connect();
$stmt = $this->mysqli->prepare($sql);
if (!$stmt) throw new Exception("Prepare 失敗: " . $this->mysqli->error);
if ($params) {
$typeStr = $types ? implode('', $types) : str_repeat('s', count($params));
$stmt->bind_param($typeStr, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$stmt->close();
return $row ?: false;
}
// 查詢多筆資料 (關聯陣列陣列)
public function queryAll(string $sql, array $params = [], array $types = []): array {
$this->connect();
$stmt = $this->mysqli->prepare($sql);
if (!$stmt) throw new Exception("Prepare 失敗: " . $this->mysqli->error);
if ($params) {
$typeStr = $types ? implode('', $types) : str_repeat('s', count($params));
$stmt->bind_param($typeStr, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
$rows = $result->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return $rows;
}
// 取得最後插入的 AUTO_INCREMENT ID
public function lastInsertId(): int {
$this->connect();
return $this->mysqli->insert_id;
}
// 交易控制
public function beginTransaction(): bool { $this->connect(); return $this->mysqli->begin_transaction(); }
public function commit(): bool { $this->connect(); return $this->mysqli->commit(); }
public function rollback(): bool { $this->connect(); return $this->mysqli->rollback(); }
// 關閉連線
public function close(): void {
if ($this->mysqli instanceof mysqli) {
$this->mysqli->close();
$this->mysqli = null;
}
}
// 解構子自動關閉
public function __destruct() { $this->close(); }
}
?>
<?php
require_once 'DBHelper.php';
// 1. 設定連線參數 (建議從環境變數或獨立 config.php 讀取)
$config = [
'host' => 'localhost',
'user' => 'root',
'pass' => '123456',
'dbname' => 'mymessage',
'charset' => 'utf8mb4',
];
$db = new DBHelper($config);
try {
// 2. 新增資料 (預處理防注入)
$sql = "INSERT INTO messages (title, content, created_at) VALUES (?, ?, NOW())";
$affected = $db->execute($sql, ['測試標題', '測試內容']);
if ($affected) {
$newId = $db->lastInsertId();
echo "新增成功,ID: $newId";
}
// 3. 查詢單筆
$msg = $db->queryOne("SELECT * FROM messages WHERE id = ?", [$newId]);
var_dump($msg);
// 4. 查詢列表
$list = $db->queryAll("SELECT id, title FROM messages ORDER BY id DESC LIMIT 10");
foreach ($list as $row) {
echo "{$row['id']}: {$row['title']}
";
}
// 5. 交易範例
$db->beginTransaction();
try {
$db->execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$db->execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$db->commit();
echo "轉帳成功";
} catch (Exception $e) {
$db->rollback();
echo "轉帳失敗: " . $e->getMessage();
}
} catch (Exception $e) {
error_log("DB Error: " . $e->getMessage());
echo "系統錯誤,請稍後再試";
} finally {
$db->close();
}
?>
| 特性 | 說明 |
|---|---|
| 延遲連線 | 建構子不連線,首次查詢/執行才連線,減少不必要開銷 |
| 強制預處理 | 所有公開方法皆使用 prepare() + bind_param(),從根源杜絕 SQL 注入 |
| 型別推導 | 未指定型別時預設 s (字串),MySQLi 會自動轉型,簡化呼叫 |
| 例外處理 | 錯誤拋出 Exception,呼叫端統一 try-catch,避免 die() 破壞流程 |
| 資源管理 | 每個 Statement 用完即 close();類別解構子自動關閉連線 |