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

PHP 單例模式完整指南:核心原理、實現細節與資料庫連接實戰

     編輯:PHP 綜合知識

一、單例模式核心概念

單例模式 確保某個類在全局範圍內僅存在一個實例,並提供一個全局訪問點獲取該實例。它不創建副本,而是返回對內部儲存實例的引用。

二、「三私一公」結構鐵律

要素修飾符作用
儲存實例的靜態成員變量private static防止外部直接修改實例引用
構造函數private禁止 new ClassName() 創建新實例
克隆方法 __clone()private禁止 clone $instance 複製實例
獲取實例的公共靜態方法public static統一入口,通常命名 getInstance()

三、為何 PHP 專案離不開單例?

  1. 資源密集型連接復用:數據庫連接、Redis 連接、HTTP 客戶端、日誌寫入器等,頻繁 `new` 會耗盡文件描述符、連接池、記憶體。
  2. 全局狀態統一管理:配置中心、路由表、事件總線、服務容器等,天然適合單一實例協調全局。
  3. 避免重複初始化開銷:解析大型配置文件、建立 TLS 連接、載入機器學習模型等昂貴操作,僅執行一次。

四、標準實現:資料庫連接類

<?php
class Database {
    // 1. 私有靜態實例容器
    private static ?self $instance = null;

    // 2. 連接資源(真實業務中為 PDO/mysqli 實例)
    private $connection;

    // 3. 私有構造函數:完成昂貴的初始化
    private function __construct() {
        $dsn = sprintf(
            'mysql:host=%s;dbname=%s;charset=utf8mb4',
            self::DB_HOST, self::DB_NAME
        );
        $this->connection = new PDO($dsn, self::DB_USER, self::DB_PWD, [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ]);
    }

    // 4. 禁止克隆
    private function __clone() {}

    // 5. 禁止反序列化(防止繞過構造函數)
    public function __wakeup() {
        throw new \Exception("Cannot unserialize singleton");
    }

    // 6. 統一訪問入口(雙重檢查鎖模式,PHP 單執行緒下簡化版)
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 7. 業務方法代理到底層連接
    public function query(string $sql, array $params = []): \PDOStatement {
        $stmt = $this->connection->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }

    // 常量配置(實際項目建議從環境變量/配置中心讀取)
    private const DB_HOST = '127.0.0.1';
    private const DB_NAME = 'app_db';
    private const DB_USER = 'app_user';
    private const DB_PWD  = 'secret';
}

// --- 使用示例 ---
$db = Database::getInstance();
$users = $db->query('SELECT * FROM users WHERE status = ?', [1])->fetchAll();

五、進階話題與避坑指南

1. 晚期靜態綁定 (LSB) 支援繼承

若需子類各自擁有單例,使用 `static::` 而非 `self::`:

abstract class Singleton {
    private static $instances = [];
    final protected function __construct() {}
    final protected function __clone() {}
    final public static function getInstance() {
        $cls = static::class; // 關鍵:獲取實際調用的子類名
        if (!isset(self::$instances[$cls])) {
            self::$instances[$cls] = new $cls();
        }
        return self::$instances[$cls];
    }
}

class MySQLDB extends Singleton { }
class RedisDB extends Singleton { }
// MySQLDB::getInstance() 與 RedisDB::getInstance() 互不干擾

2. 單元測試的「單例難題」

單例的全局狀態會導致測試相互污染。解決方案:

  • 提供 `resetInstance()` 方法(僅 `public` 在測試環境,生產環境可用 `@internal` 標註)。
  • 依賴注入 (DI) 容器管理生命週期,測試時替換為 Mock 對象(推薦架構級方案)。

3. 多進程/協程環境下的並發安全

  • PHP-FPM (預分叉多進程):每個進程獨立記憶體,單例在進程內唯一,進程間不共享,無競態。
  • Swoole/RoadRunner/Workerman (常駐進程/協程):需加進程級鎖(`flock`、Redis 分佈式鎖、Channel)保護初始化區段。

4. 什麼時候不應該用單例

  • 狀態頻繁變更的業務實體(如 User、Order)。
  • 需要多實例隔離的場景(如多租戶數據庫連接)。
  • 純工具類(無狀態),直接用 `static` 方法或命名空間函數更簡潔。

六、現代框架中的單例:服務容器與依賴注入

Laravel、Symfony、Hyperf 等框架不再手寫單例類,而是將類註冊為 singleton 綁定到容器:

// Laravel 服務提供者中
$this->app->singleton(Database::class, function () {
    return new Database(config('database'));
});

// 控制器構造函數自動注入(容器保證同一請求內單例)
public function __construct(protected Database $db) {}

優勢:解耦創建邏輯、支援介面綁定、便於測試替換、生命週期可視化管理。

結語

單例模式在 PHP 中最核心的價值是 「昂貴資源的單一化管理」。掌握「三私一公」結構、LSB 繼承技巧、測試隔離方案,並理解其在現代框架容器中的演進形態,才能在架構決策中遊刃有餘——用對地方是藝術,到處濫用是災難。

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