首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

一個經典的 PHP MySQL 封裝類深度解析與現代化遷移指南

     編輯:PHP 綜合知識

前言

這是一個典型的 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() { ... }
}

亮點設計分析

1. 靈活的建構子配置注入

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)的配置綁定思想皆源於此。

2. 統一的連線管理

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。

3. 豐富的結果集獲取方法

提供 7 種 fetch* 方法覆蓋常見場景:

  • fetchOne:單值(如 SELECT COUNT(*))
  • fetchCol:單欄陣列(如 SELECT id FROM users)
  • fetchRow:單行索引陣列
  • fetchAssoc:單行關聯陣列
  • fetchAll:多行陣列,支援 MYSQL_ASSOC/NUM/BOTH
  • fetchPairs:鍵值對陣列 [col1 => col2]

這種「語義化方法名」極大降低了記憶負擔,現代 ORM 的 pluck()、first()、get() 皆為此思想延伸。

4. 寫入操作的 SQL 組裝封裝

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

現代化遷移方案:基於 PDO 的同介面重寫

<?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);
    }
});

遷移檢查清單

  1. 全域搜尋:mysql_connect、mysql_query、mysql_fetch_*、mysql_* 所有函數
  2. 識別注入點:所有直接拼接變數的 SQL,改為預處理佔位符 ? 或 :name
  3. 補充錯誤處理:統一捕獲 PDOException,記錄日誌、回傳友善錯誤訊息
  4. 字元集升級:utf8 → utf8mb4(支援 Emoji)
  5. 測試覆蓋:針對查詢、寫入、交易、異常場景編寫單元測試

總結

這個 Mysql 類別是 PHP 早期資料庫封裝的縮影:介面設計超前,底層實現落後。其方法命名、參數風格、語義化封裝至今仍具指導意義。遷移現代化的核心不在於「換個驅動」,而在於建立「參數化查詢」為核心的安全基因,並引入異常處理、交易控制、連線韌性等企業級特性。若專案規模較大,建議直接採用成熟 ORM(Eloquent、Doctrine)或查詢構建器,專注於業務邏輯而非重複造輪子。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved