這是一個典型的 PHP 4/5 時代 MySQL 封裝類,雖然使用已被移除的 mysql_* 擴展,但其介面設計、方法命名、錯誤處理模式具有很強的教學意義。本文將深度解析其架構,並提供基於 PDO/MySQLi 的現代化遷移方案。
class Mysql {
// 連線屬性
private $conn, $host, $username, $password, $dbname, $pconnect, $charset;
// 建構子:支援陣列注入配置
public function __construct(array $params = null) { ... }
// 核心連線邏輯
public function connect() { ... }
// 查詢執行與結果集獲取(7 種方法)
public function query($sql) { ... }
public function fetchOne($sql) { ... }
public function fetchCol($sql) { ... }
public function fetchRow($sql) { ... }
public function fetchAssoc($sql) { ... }
public function fetchAll($sql, $type = MYSQL_ASSOC) { ... }
public function fetchPairs($sql) { ... }
// 寫入操作封裝
public function insert($table, array $bind) { ... }
public function update($table, array $bind, $where = '') { ... }
public function delete($table, $where = '') { ... }
// 工具方法
public function getLastInsertId() { ... }
public function affectedRows() { ... }
public function close() { ... }
}
public function __construct(array $params = null) {
if (!empty($params)) {
foreach ($params as $k => $v) {
$this->$k = $v; // 動態屬性賦值
}
}
}
// 使用方式
$db = new Mysql([
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'myapp',
'charset' => 'utf8',
'pconnect' => false
]);
這種「屬性映射」模式簡潔優雅,現代框架(Laravel、Symfony)的配置綁定思想皆源於此。
public function connect() {
$fun = $this->pconnect ? 'mysql_pconnect' : 'mysql_connect';
$this->conn = $fun($this->host, $this->username, $this->password);
$this->conn && $this->query('set names ' . $this->charset);
$this->conn && mysql_select_db($this->dbname, $this->conn);
}
支援長連線(pconnect),自動設定字元集、選擇資料庫。但缺乏連線失敗的異常拋出,僅靠後續查詢時檢查 $this->conn。
提供 7 種 fetch* 方法覆蓋常見場景:
fetchOne:單值(如 SELECT COUNT(*))fetchCol:單欄陣列(如 SELECT id FROM users)fetchRow:單行索引陣列fetchAssoc:單行關聯陣列fetchAll:多行陣列,支援 MYSQL_ASSOC/NUM/BOTHfetchPairs:鍵值對陣列 [col1 => col2]這種「語義化方法名」極大降低了記憶負擔,現代 ORM 的 pluck()、first()、get() 皆為此思想延伸。
public function insert($table, array $bind) {
$cols = array_keys($bind);
$vals = array_values($bind);
// 注意:直接拼接值,存在 SQL 注入風險!
$sql = "INSERT INTO $table (`" . implode('`, `', $cols) . "`) VALUES ('" . implode("', '", $vals) . "')";
return $this->query($sql) && $this->affectedRows();
}
⚠️ 致命缺陷:值直接拼接進 SQL 字串,完全無防 SQL 注入能力。僅在 SafeString 做了簡單 HTML 實體轉換,對資料庫層面無效。
| 缺陷 | 風險等級 | 說明 |
|---|---|---|
使用已移除的 mysql_* 擴展 | 🔴 致命 | PHP 7.0+ 完全不可用,必須遷移 |
| SQL 直接拼接變數 | 🔴 致命 | 全盤 SQL 注入風險,無預處理機制 |
| 錯誤處理機制缺失 | 🟠 高 | 無異常拋出、無錯誤代碼、無日誌記錄 |
| 無交易支援 | 🟡 中 | 無法保證多步寫入的原子性 |
| 無連線池/重連機制 | 🟡 中 | 長連線斷開後無法自動恢復 |
字元集硬編碼 set names | 🟢 低 | 現代驅動建議用 DSN 指定 charset |
<?php
declare(strict_types=1);
class Database
{
private PDO $pdo;
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_FOUND_ROWS => true, // 讓 affectedRows 返回匹配行數
];
if (!empty($config['persistent'])) {
$options[PDO::ATTR_PERSISTENT] = true;
}
$this->pdo = new PDO($dsn, $config['username'], $config['password'], $options);
}
// 核心查詢:支援預處理參數綁定
public function query(string $sql, array $params = []): PDOStatement {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
// 語義化獲取方法
public function fetchOne(string $sql, array $params = []): mixed {
return $this->query($sql, params)->fetchColumn();
}
public function fetchCol(string $sql, array $params = []): array {
return $this->query($sql, params)->fetchAll(PDO::FETCH_COLUMN);
}
public function fetchRow(string $sql, array $params = []): ?array {
return $this->query($sql, params)->fetch() ?: null;
}
public function fetchAll(string $sql, array $params = []): array {
return $this->query($sql, params)->fetchAll();
}
public function fetchPairs(string $sql, array $params = []): array {
$rows = $this->fetchAll($sql, params);
return array_column($rows, 1, 0); // [col1 => col2]
}
// 安全寫入操作:自動預處理
public function insert(string $table, array $data): int {
$cols = implode(', ', array_map(fn($c) => "`$c`", array_keys($data)));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO `$table` ($cols) VALUES ($placeholders)";
$this->query($sql, array_values($data));
return (int)$this->pdo->lastInsertId();
}
public function update(string $table, array $data, string $where, array $whereParams = []): int {
$set = implode(', ', array_map(fn($c) => "`$c` = ?", array_keys($data)));
$sql = "UPDATE `$table` SET $set WHERE $where";
$params = array_merge(array_values($data), $whereParams);
return $this->query($sql, $params)->rowCount();
}
public function delete(string $table, string $where, array $params = []): int {
$sql = "DELETE FROM `$table` WHERE $where";
return $this->query($sql, $params)->rowCount();
}
// 交易支援
public function transaction(callable $callback): mixed {
$this->pdo->beginTransaction();
try {
$result = $callback($this);
$this->pdo->commit();
return $result;
} catch (Throwable $e) {
$this->pdo->rollBack();
throw $e;
}
}
public function getPdo(): PDO {
return $this->pdo;
}
}
// 使用範例
$db = new Database([
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'myapp',
'charset' => 'utf8mb4'
]);
// 安全查詢
$user = $db->fetchRow('SELECT * FROM users WHERE email = ? AND status = ?', [$email, 1]);
// 批次插入
$db->transaction(function($db) use ($users) {
foreach ($users as $user) {
$db->insert('users', $user);
}
});
mysql_connect、mysql_query、mysql_fetch_*、mysql_* 所有函數? 或 :namePDOException,記錄日誌、回傳友善錯誤訊息utf8 → utf8mb4(支援 Emoji)這個 Mysql 類別是 PHP 早期資料庫封裝的縮影:介面設計超前,底層實現落後。其方法命名、參數風格、語義化封裝至今仍具指導意義。遷移現代化的核心不在於「換個驅動」,而在於建立「參數化查詢」為核心的安全基因,並引入異常處理、交易控制、連線韌性等企業級特性。若專案規模較大,建議直接採用成熟 ORM(Eloquent、Doctrine)或查詢構建器,專注於業務邏輯而非重複造輪子。