單例模式作為創建型模式的經典代表,在 PHP 數據庫訪問層有著極高的應用價值。本文從概念本質、PHP 語言特性下的適用性、三大核心優勢、完整 PDO 實現代碼四個維度深度剖析,助你在實戰中正確落地。
PHP 採用「請求隔離」架構:每個 HTTP 請求啟動獨立進程/線程,腳本執行結束時所有資源自動回收。因此 PHP 單例的生命周期天然限制在單次請求內,不存在跨請求的實例復用。這看似削弱了「避免重複實例化」的傳統價值,但實際上在單次請求中卻能發揮關鍵作用。
典型業務請求涉及 10~50 次數據庫交互。若每次 new PDO() 都會觸發:TCP 三次握手、SSL 握手、認證協商、字符集協商。單例模式將連接建立延遲到首次調用,後續復用同一連接,顯著降低延遲與資源佔用。
參考 Zend Framework 的 FrontController,單例可作為配置註冊中心:字符集、時區、SQL 模式、錯誤模式、屬性設置等集中在 conn() 方法配置,避免分散在各業務邏輯中導致不一致。
所有 SQL 經過單例的 fetch()、execute() 方法,僅需在這兩個入口植入:
① 慢查詢日誌(微秒級耗時統計)
② 完整 SQL 重構(綁定參數替換)
③ 審計追蹤(用戶、IP、請求 ID)
④ 異常熔斷(連續失敗自動降級)
避免業務代碼到處散佈 var_dump/error_log。
class DB {
private static $connect = null;
// 禁止外部實例化
private function __construct() { return false; }
private function __clone() { return false; }
// 建立真實連接
private static function connect() {
$pdo = new PDO(
'mysql:host=localhost;dbname=dbname;charset=utf8mb4',
'root', '',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用原生預處理
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
]
);
return $pdo;
}
// 獲取單例連接
public static function getInstance() {
if (self::$connect === null) {
self::$connect = self::connect();
}
return self::$connect;
}
// 查詢單行
public static function fetch($sql, array $params = []) {
$stmt = self::getInstance()->prepare($sql);
$stmt->execute($params);
return $stmt->fetch();
}
// 查詢多行
public static function fetchAll($sql, array $params = []) {
$stmt = self::getInstance()->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
// 執行增刪改
public static function execute($sql, array $params = []) {
$stmt = self::getInstance()->prepare($sql);
return $stmt->execute($params);
}
// 事務支持
public static function transaction(callable $callback) {
$pdo = self::getInstance();
$pdo->beginTransaction();
try {
$result = $callback($pdo);
$pdo->commit();
return $result;
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
}
}
if (null === self::$connect) 習慣可避免誤調用導致的重複連接。ERRMODE_EXCEPTION 統一異常處理、EMULATE_PREPARES = false 防注入、BUFFERED_QUERY 釋放服務端游標。charset=utf8mb4 替代舊版 query('SET NAMES'),確保連接建立即生效。transaction() 高階 API,自動處理提交/回滾/異常傳播,業務代碼更簡潔。// 業務層調用
$user = DB::fetch('SELECT * FROM users WHERE id = ?', [$uid]);
$orders = DB::fetchAll('SELECT * FROM orders WHERE user_id = ? AND status = ?', [$uid, 1]);
DB::transaction(function($pdo) use ($orderId, $amount) {
DB::execute('UPDATE accounts SET balance = balance - ? WHERE user_id = ?', [$amount, $uid]);
DB::execute('INSERT INTO transactions (order_id, amount, type) VALUES (?, ?, "pay")', [$orderId, $amount]);
});
① 多數據庫實例場景(主從分離、多租戶) → 改用連接池或工廠模式
② 長運行 CLI 腳本(守護進程) → 需要心跳檢測與重連機制
③ 單元測試環境 → 依賴注入容器替換為 Mock 實例