原文使用已棄用的 mysql_* 函數(PHP 7.0 移除),存在 SQL 注入風險且無法使用預處理語句。本文在保留原有功能邏輯的基礎上,重構為基於 PDO 的現代化數據庫操作類,支持預處理、事務、異常處理,並修復原代碼中的安全漏洞。
警告:mysql_connect、mysql_query等函數自 PHP 5.5 棄用,PHP 7.0 完全移除。所有新項目必須使用 PDO 或 mysqli。
<?php
/**
* 簡易 PDO 數據庫操作類
* 支持預處理、事務、錯誤異常模式
*/
class Database
{
private \PDO $pdo;
private string $tablePrefix = '';
public function __construct(array $config)
{
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset'] ?? 'utf8mb4'}";
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, // 拋出異常
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, // 關聯陣列
\PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES {$config['charset'] ?? 'utf8mb4'}",
];
try {
$this->pdo = new \PDO($dsn, $config['user'], $config['pass'], $options);
} catch (\PDOException $e) {
// 生產環境應記錄日誌而非直接輸出
throw new \RuntimeException('數據庫連接失敗: ' . $e->getMessage());
}
}
// 設置表前綴
public function setTablePrefix(string $prefix): self
{
$this->tablePrefix = $prefix;
return $this;
}
// 通用查詢:返回所有行
public function fetchAll(string $sql, array $params = []): array
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
// 通用查詢:返回單行
public function fetchOne(string $sql, array $params = []): ?array
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetch() ?: null;
}
// 通用查詢:返回單值
public function fetchValue(string $sql, array $params = [])
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn();
}
// 執行增刪改:返回影響行數
public function execute(string $sql, array $params = []): int
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->rowCount();
}
// 插入並返回自增 ID
public function insert(string $table, array $data): int
{
$fields = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO `{$this->tablePrefix}{$table}` ($fields) VALUES ($placeholders)";
$this->execute($sql, $data);
return (int)$this->pdo->lastInsertId();
}
// 更新
public function update(string $table, array $data, string $where, array $whereParams = []): int
{
$set = implode(', ', array_map(fn($k) => "`$k` = :$k", array_keys($data)));
$sql = "UPDATE `{$this->tablePrefix}{$table}` SET $set WHERE $where";
return $this->execute($sql, array_merge($data, $whereParams));
}
// 刪除
public function delete(string $table, string $where, array $params = []): int
{
$sql = "DELETE FROM `{$this->tablePrefix}{$table}` WHERE $where";
return $this->execute($sql, $params);
}
// 事務支持
public function beginTransaction(): void { $this->pdo->beginTransaction(); }
public function commit(): void { $this->pdo->commit(); }
public function rollBack(): void { $this->pdo->rollBack(); }
// 獲取底層 PDO 實例(高級用法)
public function getPdo(): \PDO { return $this->pdo; }
}
?>
<?php
require_once 'Database.php';
$config = [
'host' => 'localhost',
'user' => 'root',
'pass' => '123456',
'dbname' => 'products',
'charset'=> 'utf8mb4',
];
$db = new Database($config);
// 查詢分類列表
$categories = $db->fetchAll("SELECT * FROM `category`");
foreach ($categories as $row) {
echo "ID: {$row['id']}, Name: {$row['name']}<br>";
}
// 安全插入(預處理防注入)
$newId = $db->insert('category', ['name' => '新分類']);
echo "插入成功,ID: $newId<br>";
// 安全更新
$affected = $db->update('category', ['name' => '更新名稱'], 'id = :id', ['id' => $newId]);
echo "更新行數: $affected<br>";
// 安全刪除
$deleted = $db->delete('category', 'id = :id', ['id' => $newId]);
echo "刪除行數: $deleted<br>";
?>
| 維度 | 原 mysql_* 代碼 | 重構 PDO 類 |
|---|---|---|
| SQL 注入防護 | 無(字符串拼接) | 預處理參數綁定 |
| 字符集 | utf8(不支持 Emoji) | utf8mb4(完整 Unicode) |
| 錯誤處理 | 手動判斷返回值 | 異常模式,統一 try-catch |
| 事務支持 | 無 | 內建 beginTransaction/commit/rollBack |
| 代碼復用 | 分散的過程式代碼 | 封裝 CRUD、鏈式調用 |
| PHP 版本兼容 | ≤ 5.6 | ≥ 5.1(推薦 7.4+) |
.env + vlucas/phpdotenv)。INSERT ... VALUES (...), (...) 或事務包裹。fetchAll 會佔用大量內存,改用 while ($row = $stmt->fetch()) 游標式讀取。