單例模式確保某個類別在全域僅有一個實例,並提供全域存取點。適用於資料庫連線、配置管理、日誌記錄器等「全域唯一」資源。
<?php
class Database {
private static ?self $instance = null;
private PDO $pdo;
// 私有建構子,防止外部 new
private function __construct() {
$this->pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'user', 'pass');
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
// 禁止克隆
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 query(string $sql, array $params = []): PDOStatement {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
}
// === 使用方式 ===
$db = Database::getInstance();
$users = $db->query('SELECT * FROM users WHERE status = ?', [1])->fetchAll();
// 任何地方呼叫 getInstance() 都得到同一實例
$db2 = Database::getInstance();
var_dump($db === $db2); // true
?>
| 要素 | 實作方式 | 目的 |
|---|---|---|
| 私有靜態屬性儲存實例 | private static ?self $instance = null; | 保存唯一實例參考 |
| 私有建構子 | private function __construct() | 阻止 new Class() |
私有 __clone | private function __clone() | 阻止 clone $obj |
私有 __wakeup | public function __wakeup() { throw ... } | 阻止反序列化產生新實例 |
| 靜態工廠方法 | public static function getInstance() | 提供全域存取點 |
若需根據不同 DSN 建立多個單例(如主庫/從庫),可改用陣列儲存:
<?php
class Database {
private static array $instances = [];
private PDO $pdo;
private function __construct(string $dsn, string $user, string $pass) {
$this->pdo = new PDO($dsn, $user, $pass);
}
public static function getInstance(string $name = 'default', string $dsn = '', string $user = '', string $pass = ''): self {
if (!isset(self::$instances[$name])) {
if (!$dsn) throw new InvalidArgumentException("首次建立 '$name' 需提供 DSN");
self::$instances[$name] = new self($dsn, $user, $pass);
}
return self::$instances[$name];
}
// ... query 方法同前
}
// 使用
$master = Database::getInstance('master', 'mysql:host=10.0.0.1;dbname=app', 'rw', 'secret');
$slave = Database::getInstance('slave', 'mysql:host=10.0.0.2;dbname=app', 'ro', 'secret');
// 再次取得
$master2 = Database::getInstance('master'); // 參數可省略
?>
在 Laravel、Symfony、ThinkPHP 等現代框架中,**單例由容器管理**,類別本身不需實作單例介面:
<?php
// Laravel 服務提供者註冊
$this->app->singleton(Database::class, function ($app) {
return new Database(config('database.connections.mysql'));
});
// 控制器中型別提示自動注入
class UserController {
public function index(Database $db) {
return $db->query('SELECT * FROM users');
}
}
?>
優點:解耦、易測試(可注入 Mock)、支援多實例、生命週期由容器統一控制。
單例模式簡單強大,但濫用會導致強耦合、難以單元測試。現代開發優先選擇依賴注入容器管理物件生命週期。