單例模式確保類在全局僅有一個實例,並提供統一訪問點。PHP 中最典型應用是**數據庫連接管理**,避免重複建立連接浪費資源。本文從基礎實現進階到多數據庫場景,並討論繼承陷阱與現代替代方案。
class Database {
private static $instance = null;
private $pdo;
// 私有化構造、克隆、反序列化
private function __construct() {
$this->pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
private function __clone() {}
public function __wakeup() { throw new \Exception('Cannot unserialize singleton'); }
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getPdo(): PDO { return $this->pdo; }
}
// 使用
$pdo = Database::getInstance()->getPdo();
$stmt = $pdo->query('SELECT 1');
class DbManager {
private static $instances = [];
private function __construct() {}
public static function getConnection(string $name, array $config): PDO {
if (!isset(self::$instances[$name])) {
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4";
self::$instances[$name] = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
return self::$instances[$name];
}
public static function closeAll(): void {
self::$instances = [];
}
}
// 配置驅動
$pdoMain = DbManager::getConnection('main', [
'host' => '127.0.0.1', 'dbname' => 'main', 'user' => 'u', 'pass' => 'p'
]);
$pdoLog = DbManager::getConnection('log', [
'host' => '127.0.0.1', 'dbname' => 'log_db', 'user' => 'u', 'pass' => 'p'
]);
若 class Child extends Database 並調用 Child::getInstance(),靜態屬性 $instance 屬於父類,導致父子類共享同一實例。解決辦法:
static::$instance 替代 self::$instance。$key = get_called_class() . ':' . $dsnHash。// Late Static Binding 版
class Database {
private static $instances = [];
private function __construct() {}
public static function getInstance(): self {
$class = get_called_class();
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new $class();
}
return self::$instances[$class];
}
}
單例模式屬於「服務定位器」反模式,難以測試、隱藏依賴。現代框架(Laravel、Symfony、Hyperf)均採用 **DI 容器** 管理服務生命週期:
$container->singleton(Database::class, function () {
return new PDO(...);
});
// 控制器構造函數自動注入
class UserController {
public function __construct(private Database $db) {}
}
單例模式在**舊專案快速重構**、**無框架腳本**、**全局配置/日誌/緩存客戶端**等場景仍有價值。但新架構設計中,優先選擇 **DI 容器** 管理共享服務,獲得更好的可測試性、擴展性與架構清晰度。