單例模式是創建型設計模式中最簡單也最實用的一種,確保全域只有一個實例,並提供全域存取點。在 PHP 中,資料庫連線是單例模式的經典應用場景,避免重複 new 造成連線資源浪費。本文完整解析單例模式三大核心要素、實現細節與實戰程式碼。
private __construct() 防止外部 newpublic static getInstance() 提供唯一取得途徑<?php
/**
* 資料庫單例連線類別
* 確保整個請求週期內只建立一個 DB 連線
*/
class Db {
// 靜態屬性保存唯一實例
private static ?self $_instance = null;
// 靜態屬性保存連線資源
private static $dbConnect = null;
// 資料庫設定(實際專案建議從設定檔讀取)
private array $_dbConfig = [
'host' => '127.0.0.1',
'user' => 'root',
'password' => '',
'database' => 'yii2basic',
'charset' => 'utf8mb4',
];
// 1. 私有建構子:防止外部 new Db()
private function __construct() {}
// 2. 私有克隆:防止 clone $db
private function __clone() {
trigger_error('單例模式禁止克隆', E_USER_ERROR);
}
// 3. 私有反序列化:防止 unserialize()
public function __wakeup() {
trigger_error('單例模式禁止反序列化', E_USER_ERROR);
}
// 4. 公開靜態方法:取得唯一實例
public static function getInstance(): self {
if (self::$_instance === null) {
self::$_instance = new self();
}
return self::$_instance;
}
// 5. 連線方法:建立並回傳 mysqli 連線
public function connect(): mysqli {
if (self::$dbConnect !== null) {
return self::$dbConnect; // 已連線直接返回
}
$cfg = $this->_dbConfig;
self::$dbConnect = @new mysqli(
$cfg['host'], $cfg['user'], $cfg['password'], $cfg['database']
);
if (self::$dbConnect->connect_error) {
throw new Exception(
"MySQL 連線失敗:{$cfg['host']} - " . self::$dbConnect->connect_error
);
}
// 設定字元集
self::$dbConnect->set_charset($cfg['charset']);
return self::$dbConnect;
}
}
// 使用範例
try {
$db = Db::getInstance();
$conn = $db->connect();
echo "資料庫連線成功!Server version: {$conn->server_info}";
} catch (Exception $e) {
echo "錯誤:{$e->getMessage()}";
}
?>
private static ?self $_instance = null; // 可為 null 或 self
public static function getInstance(): self { ... } // 回傳型別宣告
PHP 單執行緒請求模型下不需雙重檢查,但 Swoole/RoadRunner 等常駐記憶體環境需注意:
public static function getInstance(): self {
if (self::$_instance === null) {
// 這裡可加入鎖定機制(如 Semaphore)
if (self::$_instance === null) {
self::$_instance = new self();
}
}
return self::$_instance;
}
public function __sleep() {
throw new Exception('單例不可序列化');
}
public function __wakeup() {
throw new Exception('單例不可反序列化');
}
| 理由 | 說明 |
|---|---|
| 資源節省 | 避免每次查詢都 new mysqli(),減少 TCP 握手與認證開銷 |
| 全域狀態管理 | 配置、快取、日誌等跨模組共享資源的統一入口 |
| 除錯便利 | 所有 SQL 經單一類別,易植入效能分析、慢查詢日誌 |
| 請求隔離 | PHP-FPM 每請求重置,單例生命週期自然限制在單一請求 |
框架(Laravel、Symfony)推薦使用 服務容器 管理單例生命週期:
// Laravel 服務容器綁定單例
$this->app->singleton('db', function () {
return new Db(config('database'));
});
// 使用時自動解析
$db = app('db'); // 或注入建構子
優點:可測試性高、解耦合、支援多實例切換(如測試用 SQLite)。
server gone away → 需實作重連機制privategetInstance() 回傳型別宣告disconnect() 釋放資源單例模式在 PHP 資料庫連線管理上極具實用價值,但需謹慎使用,避免成為「全域變數的偽裝」。現代專案建議搭配 DI 容器管理生命週期,兼顧單例優勢與可測試性。