首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 設計模式:單例模式實現與多數據庫連接管理實戰

     編輯:PHP 相關知識

單例模式確保類在全局僅有一個實例,並提供統一訪問點。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 屬於父類,導致父子類共享同一實例。解決辦法:

  1. Late Static Binding (PHP 5.3+):用 static::$instance 替代 self::$instance。
  2. 註冊表鍵名含類名:$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];
    }
}

現代替代方案:依賴注入容器 (PSR-11)

單例模式屬於「服務定位器」反模式,難以測試、隱藏依賴。現代框架(Laravel、Symfony、Hyperf)均採用 **DI 容器** 管理服務生命週期:

$container->singleton(Database::class, function () {
    return new PDO(...);
});
// 控制器構造函數自動注入
class UserController {
    public function __construct(private Database $db) {}
}
  • ✅ 易於單元測試(Mock 替換)。
  • ✅ 依賴關係顯式化、可視化。
  • ✅ 支援作用域(Singleton、Request、Transient)。

總結

單例模式在**舊專案快速重構**、**無框架腳本**、**全局配置/日誌/緩存客戶端**等場景仍有價值。但新架構設計中,優先選擇 **DI 容器** 管理共享服務,獲得更好的可測試性、擴展性與架構清晰度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved