單例模式確保某個類別在整個應用程式生命週期中「只有一個實例」,並提供全域存取點。常見用途:資料庫連線、日誌記錄器、配置管理器。本文從問題導向出發,對比 PHP 4 與 PHP 5/7/8 的實作差異,並探討現代最佳實踐。
問題:如何保證類別只被實例化一次,且易於存取?
方案:
private,防止外部 newgetInstance() 回傳唯一實例PHP 4 無 private、靜態屬性不支援物件參考,需用全域變數或靜態陣列變通:
class DbConn {
function DbConn($fromGetInstance = false) {
if (M_E != $fromGetInstance) { // 簡易保護
trigger_error('DbConn 是單例,請勿直接實例化');
}
}
function &getInstance() {
static $instance = [];
if (!$instance) {
$instance[0] =& new DbConn(M_E);
}
return $instance[0];
}
}
呼叫方式:$conn =& DbConn::getInstance(); (需傳回參考)
PHP 5 引入可見度關鍵字、統一建構子 __construct、靜態屬性支援物件,實作大幅簡化:
class DbConn {
private static $instance = null;
private function __construct() {
// 初始化資料庫連線...
}
// 禁止克隆
private function __clone() {}
// 禁止反序列化
public function __wakeup() {
throw new Exception("不能反序列化單例");
}
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// 業務方法
public function query(string $sql) { /* ... */ }
}
private __construct:阻擋 new DbConn()private __clone:阻擋 clone $db__wakeup:阻擋 unserialize() 產生新實例: self (PHP 7.4+) 提升程式碼可讀性與靜態分析能力PHP 通常運行於單執行緒環境 (mod_php, php-fpm),單例在單一請求內自然安全。但若使用 Swoole、RoadRunner 等長驻程式模型,需注意:
單例常被濫用,缺點包括:
現代替代方案:
singleton 共享實例class Database {
private static $instance = null;
private $pdo;
private function __construct() {
$dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4";
$this->pdo = new PDO($dsn, 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// 代理 PDO 方法
public function __call(string $method, array $args) {
return $this->pdo->$method(...$args);
}
}
// 使用方式
$db = Database::getInstance();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([1]);
$user = $stmt->fetch();
單例模式在 PHP 5+ 實作簡潔優雅,但現代架構傾向透過 DI 容器管理共享服務,而非硬編碼單例。理解單例本質與限制,能幫助開發者在適當場合做出正確架構決策。對於資料庫連線、快取客戶端等真正需要「全程序唯一實例」的場景,單例仍是可行選項,只需注意測試與長驻環境下的狀態管理。