PHPLIB 的 DB_Sql 類別預設僅支援單一資料庫連線。本文示範如何透過繼承與工廠模式擴展它,實現多資料庫連線管理,並保持向相容 API。
DB_Sql 建構子固定讀取全域設定 $db_host、$db_database 等,無法在執行期切換連線。專案若需同時操作「訂單庫」、「會員庫」、「日誌庫」,需改造架構。
<?php
// 1. 定義連線設定介面
interface DBConfigProvider {
public function getConfig(string $name): array;
}
// 2. 實作:從陣列/ini/環境變數載入
class ArrayDBConfig implements DBConfigProvider {
private $configs;
public function __construct(array $configs) { $this->configs = $configs; }
public function getConfig(string $name): array {
return $this->configs[$name] ?? throw new InvalidArgumentException("Unknown DB: $name");
}
}
// 3. 擴展 DB_Sql 支援指定連線
class MultiDB_Sql extends DB_Sql {
private static $instances = [];
private static $configProvider;
public static function setConfigProvider(DBConfigProvider $provider) {
self::$configProvider = $provider;
}
// 工廠方法:取得/建立命名連線
public static function getInstance(string $name = 'default'): self {
if (!isset(self::$instances[$name])) {
$cfg = self::$configProvider->getConfig($name);
$instance = new self();
$instance->Host = $cfg['host'];
$instance->Database = $cfg['database'];
$instance->User = $cfg['user'];
$instance->Password = $cfg['pass'];
$instance->Type = $cfg['type'] ?? 'mysql'; // mysql, pgsql, oracle, mssql
$instance->connect();
self::$instances[$name] = $instance;
}
return self::$instances[$name];
}
// 禁止直接 new,強制使用工廠
protected function __construct() { parent::__construct(); }
private function __clone() {}
public function __wakeup() { throw new Exception("Cannot unserialize"); }
}
// 4. 使用範例
MultiDB_Sql::setConfigProvider(new ArrayDBConfig([
'default' => ['host'=>'localhost','database'=>'main','user'=>'u','pass'=>'p','type'=>'mysql'],
'orders' => ['host'=>'db2','database'=>'orders','user'=>'u','pass'=>'p','type'=>'mysql'],
'logs' => ['host'=>'db3','database'=>'logs','user'=>'u','pass'=>'p','type'=>'pgsql'],
]));
// 取得不同連線
$mainDB = MultiDB_Sql::getInstance('default');
$orderDB = MultiDB_Sql::getInstance('orders');
$logDB = MultiDB_Sql::getInstance('logs');
$mainDB->query("SELECT * FROM users WHERE id=1");
$orderDB->query("SELECT * FROM orders WHERE user_id=1");
$logDB->query("INSERT INTO access_log (ip, url) VALUES ('1.2.3.4', '/home')");
class RobustMultiDB_Sql extends MultiDB_Sql {
private $lastPing = 0;
private $pingInterval = 30; // 秒
public function query($sql) {
$this->ensureConnected();
return parent::query($sql);
}
private function ensureConnected() {
if (time() - $this->lastPing > $this->pingInterval) {
if (!$this->ping()) {
$this->reconnect();
}
$this->lastPing = time();
}
}
private function ping(): bool {
// 簡易檢查
return @$this->query('SELECT 1') !== false;
}
private function reconnect() {
$this->connect(); // 父類別方法
}
}
MultiDB_Sql 類別檔,置於 phplib/ 目錄common.php:移除 require('db_mysql.inc'),改載入新類別config/db.php 返回陣列)new DB_Sql 或 $db = new DB_Sql,替換為 MultiDB_Sql::getInstance('name')PHPLIB 已停止維護。新專案建議直接使用 PDO 搭配 PSR-11 容器(如 PHP-DI、Symfony DI、Laravel Container):
// 容器設定
$container->set('db.main', function() {
return new PDO('mysql:host=localhost;dbname=main;charset=utf8mb4', 'u', 'p', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
});
$container->set('db.orders', fn() => new PDO('mysql:host=db2;dbname=orders...', ...));
// 使用
$mainDB = $container->get('db.main');
$orderDB = $container->get('db.orders');
此方式無繼承耦合、支援任意驅動、易於測試(Mock PDO)、符合現代 PHP 標準。