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

PHP 單例模式完整實現與資料庫連線應用實戰

     編輯:PHP 綜合知識

單例模式是創建型設計模式中最簡單也最實用的一種,確保全域只有一個實例,並提供全域存取點。在 PHP 中,資料庫連線是單例模式的經典應用場景,避免重複 new 造成連線資源浪費。本文完整解析單例模式三大核心要素、實現細節與實戰程式碼。

單例模式三大核心要素

  1. 唯一實例:類別內部自行創建並持有唯一實例
  2. 私有建構子:private __construct() 防止外部 new
  3. 全域存取:public static getInstance() 提供唯一取得途徑

完整實現程式碼

<?php
/**
 * 資料庫單例連線類別
 * 確保整個請求週期內只建立一個 DB 連線
 */
class Db {
    // 靜態屬性保存唯一實例
    private static ?self $_instance = null;
    
    // 靜態屬性保存連線資源
    private static $dbConnect = null;
    
    // 資料庫設定(實際專案建議從設定檔讀取)
    private array $_dbConfig = [
        'host'     => '127.0.0.1',
        'user'     => 'root',
        'password' => '',
        'database' => 'yii2basic',
        'charset'  => 'utf8mb4',
    ];

    // 1. 私有建構子:防止外部 new Db()
    private function __construct() {}

    // 2. 私有克隆:防止 clone $db
    private function __clone() {
        trigger_error('單例模式禁止克隆', E_USER_ERROR);
    }

    // 3. 私有反序列化:防止 unserialize()
    public function __wakeup() {
        trigger_error('單例模式禁止反序列化', E_USER_ERROR);
    }

    // 4. 公開靜態方法:取得唯一實例
    public static function getInstance(): self {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    // 5. 連線方法:建立並回傳 mysqli 連線
    public function connect(): mysqli {
        if (self::$dbConnect !== null) {
            return self::$dbConnect;  // 已連線直接返回
        }

        $cfg = $this->_dbConfig;
        self::$dbConnect = @new mysqli(
            $cfg['host'], $cfg['user'], $cfg['password'], $cfg['database']
        );

        if (self::$dbConnect->connect_error) {
            throw new Exception(
                "MySQL 連線失敗:{$cfg['host']} - " . self::$dbConnect->connect_error
            );
        }

        // 設定字元集
        self::$dbConnect->set_charset($cfg['charset']);
        
        return self::$dbConnect;
    }
}

// 使用範例
try {
    $db = Db::getInstance();
    $conn = $db->connect();
    echo "資料庫連線成功!Server version: {$conn->server_info}";
} catch (Exception $e) {
    echo "錯誤:{$e->getMessage()}";
}
?>

關鍵實現細節解析

1. 型別宣告 (PHP 7.4+)

private static ?self $_instance = null;  // 可為 null 或 self
public static function getInstance(): self { ... }  // 回傳型別宣告

2. 雙重檢查鎖定 (多執行緒環境)

PHP 單執行緒請求模型下不需雙重檢查,但 Swoole/RoadRunner 等常駐記憶體環境需注意:

public static function getInstance(): self {
    if (self::$_instance === null) {
        // 這裡可加入鎖定機制(如 Semaphore)
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
    }
    return self::$_instance;
}

3. 防止序列化破壞單例

public function __sleep() {
    throw new Exception('單例不可序列化');
}
public function __wakeup() {
    throw new Exception('單例不可反序列化');
}

為什麼 PHP 適合單例模式?

理由說明
資源節省避免每次查詢都 new mysqli(),減少 TCP 握手與認證開銷
全域狀態管理配置、快取、日誌等跨模組共享資源的統一入口
除錯便利所有 SQL 經單一類別,易植入效能分析、慢查詢日誌
請求隔離PHP-FPM 每請求重置,單例生命週期自然限制在單一請求

現代替代方案:依賴注入容器

框架(Laravel、Symfony)推薦使用 服務容器 管理單例生命週期:

// Laravel 服務容器綁定單例
$this->app->singleton('db', function () {
    return new Db(config('database'));
});

// 使用時自動解析
$db = app('db');  // 或注入建構子

優點:可測試性高、解耦合、支援多實例切換(如測試用 SQLite)。

常見誤區與反模式

  • 濫用單例:任何類別都做成單例 → 隱性耦合、難以單元測試
  • 單例攜帶狀態:單例屬性儲存請求相關資料 → 併發汙染(Swoole 環境)
  • 硬編碼設定:資料庫參數寫死類別中 → 部署環境難切換
  • 忽略連線斷開:長連線未處理 server gone away → 需實作重連機制

最佳實務檢核表

  • [ ] 建構子、克隆、喚醒皆為 private
  • [ ] 靜態實例變數型別正確
  • [ ] getInstance() 回傳型別宣告
  • [ ] 連線參數外部化(環境變數/設定檔)
  • [ ] 例外處理完整(連線失敗、查詢錯誤)
  • [ ] 支援連線重試與心跳檢測
  • [ ] 提供 disconnect() 釋放資源

單例模式在 PHP 資料庫連線管理上極具實用價值,但需謹慎使用,避免成為「全域變數的偽裝」。現代專案建議搭配 DI 容器管理生命週期,兼顧單例優勢與可測試性。

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