單例模式是創建型設計模式中最簡單、應用最廣泛的一種。它確保某個類在整個應用生命周期中只有一個實例,並提供一個全局訪問點獲取該實例。本文將從核心代碼實現、三大要素、PHP 中的特殊性、典型應用場景四個維度深度解析。
<?php
class Singleton {
// 1. 靜態私有屬性,保存唯一實例
private static $_instance = null;
// 2. 私有構造函數,防止外部 new 實例化
private function __construct() {
// 初始化邏輯(如資料庫連接、配置加載等)
}
// 3. 私有克隆方法,防止 clone 複製對象
private function __clone() {
trigger_error('克隆單例對象是被禁止的', E_USER_ERROR);
}
// 4. 私有反序列化方法,防止 unserialize 重建對象
private function __wakeup() {
trigger_error('反序列化單例對象是被禁止的', E_USER_ERROR);
}
// 5. 公共靜態方法,獲取唯一實例
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new self();
}
return self::$_instance;
}
// 業務方法示例
public function doSomething() {
echo "單例模式業務邏輯執行中...";
}
}
// 使用方式
$instance = Singleton::getInstance();
$instance->doSomething();
// 嘗試 new / clone / unserialize 都會報錯
// $obj = new Singleton(); // Fatal error
// $obj2 = clone $instance; // Fatal error
// $obj3 = unserialize(serialize($instance)); // Fatal error
?>
與 Java、C# 等長運行進程語言不同,PHP 是請求級別的生命周期:每次 HTTP 請求都會重新啟動腳本,請求結束時所有內存資源(包括單例對象)都會被銷毀。因此:
避免同一請求中多次 new PDO() 或 mysqli_connect() 導致連接數耗盡。
class Database {
private static $instance = null;
private $pdo;
private function __construct() {
$this->pdo = new PDO("mysql:host=localhost;dbname=test", 'user', 'pass');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->pdo;
}
}
// 任何地方獲取同一連接
$pdo = Database::getInstance()->getConnection();
$stmt = $pdo->query("SELECT * FROM users");
?>
全局配置只需加載一次,之後隨處可用:
class Config {
private static $instance = null;
private $settings = [];
private function __construct() {
$this->settings = require __DIR__ . '/config/app.php';
}
public static function getInstance() { /* ... */ }
public function get($key, $default = null) {
return $this->settings[$key] ?? $default;
}
}
// 使用
$dbHost = Config::getInstance()->get('database.host');
?>
統一日誌寫入入口,避免多處打開文件句柄:
class Logger {
private static $instance = null;
private $logFile;
private function __construct() {
$this->logFile = fopen(__DIR__ . '/storage/logs/app.log', 'a');
}
public static function getInstance() { /* ... */ }
public function log($level, $message) {
fwrite($this->logFile, "[" . date('Y-m-d H:i:s') . "] {$level}: {$message}\n");
}
}
Logger::getInstance()->log('INFO', '用戶登錄成功');
?>
Redis、Memcached 連接復用:
class Cache {
private static $instance = null;
private $redis;
private function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
// ...
}
?>
現代框架(Laravel、Symfony)通常不直接使用靜態 getInstance(),而是通過服務容器綁定單例:
// Laravel 風格
$app->singleton('database', function () {
return new DatabaseConnection(config('database'));
});
// 之後在控制器/服務中通過構造函數注入
class UserService {
public function __construct(DatabaseConnection $db) { /* ... */ }
}
?>
這樣做的優點:可測試性更強(可輕鬆 Mock)、解耦(不依賴具體類)、生命周期由容器統一管理。
__sleep()、__wakeup() 或直接禁用 __clone/__wakeup。PHP 單例模式雖然無法跨請求持久化實例,但在單次請求內統一資源訪問、避免重複初始化、提供全局服務入口方面價值巨大。掌握其標準實現、理解 PHP 生命周期特性、結合現代框架的依賴注入容器使用,是 PHP 開發者進階的必備技能。