new 與 clone 建立新實例
class Singleton {
// 1. 靜態私有成員保存唯一實例
private static $_instance = null;
// 2. 私有建構子,防止外部 new
private function __construct() {
echo 'Instance created';
}
// 3. 私有克隆方法,防止 clone
private function __clone() {
trigger_error('Cloning singleton is forbidden', E_USER_ERROR);
}
// 4. 公共靜態方法取得實例
public static function getInstance() {
if (self::$_instance === null) {
// 晚期靜態綁定支援繼承
$class = get_called_class();
self::$_instance = new $class();
}
return self::$_instance;
}
public function doSomething() {
echo 'Working...';
}
}
// 使用方式
$instance = Singleton::getInstance();
$instance->doSomething();
// 以下皆會失敗/報錯
// $fail1 = new Singleton(); // Fatal: private constructor
// $fail2 = clone $instance; // Fatal: private __clone
// $fail3 = Singleton::getInstance(); // 回傳同一實例
class ParentSingleton {
private static $_instance = null;
protected function __construct() {}
private function __clone() {}
public static function getInstance() {
if (static::$_instance === null) {
static::$_instance = new static(); // 關鍵:static 而非 self
}
return static::$_instance;
}
}
class ChildSingleton extends ParentSingleton {}
$child = ChildSingleton::getInstance(); // 建立 ChildSingleton 實例
使用 static:: 而非 self::,確保子類別擁有自己的單例實例,而非共用父類別實例。
PHP 採用「共享無」架構,每個請求獨立行程/執行緒,單例僅在單一請求生命週期內有效,無需雙重檢查鎖定。若使用 Swoole、RoadRunner 等長駐記憶體架構,需自行加鎖。
public function __sleep() {
throw new Exception('Singleton cannot be serialized');
}
public function __wakeup() {
throw new Exception('Singleton cannot be unserialized');
}
class ServiceRegistry {
private static $services = [];
public static function get($key, callable $factory) {
if (!isset(self::$services[$key])) {
self::$services[$key] = $factory();
}
return self::$services[$key];
}
}
// 使用
$db = ServiceRegistry::get('db', fn() => new PDO($dsn));
$cache = ServiceRegistry::get('cache', fn() => new Redis());
現代框架(Laravel、Symfony)透過容器管理單例生命週期,不建議在業務代碼中硬編碼單例,改由容器注入:
// 容器設定
$container->singleton(Database::class, fn() => new PDO($dsn));
// 控制器自動注入
class UserController {
public function __construct(private Database $db) {}
}
| 反模式 | 問題 | 替代方案 |
|---|---|---|
| 全域變數 $GLOBALS['db'] | 難測試、隱性依賴、命名衝突 | 容器注入、Service Locator |
| 靜態類別滿滿 static 方法 | 無法多型、難 Mock、違反 OCP | 實例化服務 + 介面契約 |
| 單例濫用作為「全域狀態袋」 | 耦合度極高、並發風險 | 領域事件、共享唯讀設定物件 |
✅ 適合單例:
❌ 不適合單例:
單例模式在 PHP 中實現簡單,但濫用會導致強耦合、難以測試。現代開發建議: