在Windows平台上,PHP通過COM組件調用ADO(ActiveX Data Objects)訪問Access(.mdb/.accdb)是常見的遷移或集成方案。本文詳細講解連接字符串構建、記錄集操作、常見錯誤排查,並提供生產環境可用的封裝類。
php.ini 中 extension=com_dotnet(PHP 7.4+ 需額外安裝)<?php
try {
// 1. 創建ADO連接對象
$conn = new COM("ADODB.Connection") or die("ADO Connection failed");
// 2. 構建連接字符串
// 注意:realpath() 獲取絕對路徑,驅動名稱需與安裝版本匹配
$dbPath = realpath("DATUM/cnbt.mdb");
$connStr = "Driver={Microsoft Access Driver (*.mdb)}; Dbq=$dbPath;";
// Access 2007+ (.accdb) 使用:
// $connStr = "Driver={Microsoft Access Driver (*.mdb, *.accdb)}; Dbq=$dbPath;";
// 3. 打開連接
$conn->Open($connStr);
// 4. 創建記錄集執行查詢
$rs = new COM("ADODB.Recordset");
// 參數:SQL, 連接, 游標類型(1=adOpenKeyset), 鎖類型(3=adLockOptimistic)
$rs->Open("SELECT * FROM dbo_dirs", $conn, 1, 3);
// 5. 遍歷結果集
$data = [];
while (!$rs->EOF) {
$row = [];
// 通過字段名訪問
foreach ($rs->Fields as $field) {
$row[$field->Name] = $field->Value;
}
$data[] = $row;
$rs->MoveNext();
}
// 6. 釋放資源
$rs->Close();
$conn->Close();
unset($rs, $conn);
print_r($data);
} catch (com_exception $e) {
echo "COM錯誤: " . $e->getMessage();
}
<?php
class AccessDB {
private $conn;
private $dbPath;
public function __construct(string $dbPath) {
$this->dbPath = realpath($dbPath);
if (!$this->dbPath || !file_exists($this->dbPath)) {
throw new InvalidArgumentException("數據庫文件不存在: $dbPath");
}
}
private function connect() {
if ($this->conn) return true;
$this->conn = new COM("ADODB.Connection");
$driver = strtolower(pathinfo($this->dbPath, PATHINFO_EXTENSION)) === 'accdb'
? 'Microsoft Access Driver (*.mdb, *.accdb)'
: 'Microsoft Access Driver (*.mdb)';
$connStr = "Driver={$driver}; Dbq={$this->dbPath};";
$this->conn->Open($connStr);
}
/** 執行查詢返回關聯數組 */
public function query(string $sql): array {
$this->connect();
$rs = new COM("ADODB.Recordset");
$rs->Open($sql, $this->conn, 1, 3); // adOpenKeyset, adLockOptimistic
$results = [];
while (!$rs->EOF) {
$row = [];
foreach ($rs->Fields as $field) {
$row[$field->Name] = $field->Value;
}
$results[] = $row;
$rs->MoveNext();
}
$rs->Close();
return $results;
}
/** 執行非查詢SQL(INSERT/UPDATE/DELETE) */
public function execute(string $sql): int {
$this->connect();
$affected = 0;
$this->conn->Execute($sql, $affected);
return $affected;
}
/** 獲取最後插入ID(Access不支持auto_increment標準,需查詢@@IDENTITY) */
public function lastInsertId(): int {
$this->connect();
$rs = $this->conn->Execute("SELECT @@IDENTITY");
return (int)$rs->Fields(0)->Value;
}
public function close() {
if ($this->conn) {
$this->conn->Close();
$this->conn = null;
}
}
public function __destruct() {
$this->close();
}
}
// 使用示例
$db = new AccessDB(__DIR__ . '/data/app.mdb');
$users = $db->query("SELECT id, username, email FROM users WHERE status = 1");
$db->execute("INSERT INTO logs (action, time) VALUES ('login', NOW())");
echo "新日誌ID: " . $db->lastInsertId();
$db->close();
| 錯誤現象 | 原因 | 解決方案 |
|---|---|---|
| Class 'COM' not found | PHP未啟用com_dotnet擴展 | php.ini啟用 extension=com_dotnet,重啟PHP/FPM |
| Uncaught com_exception: Provider cannot be found | Access驅動未安裝或位數不匹配 | 安裝對應位數的Access Database Engine (32位PHP裝32位驅動) |
| Permission denied / 無法打開數據庫 | 文件權限不足或被獨佔鎖定 | 授予PHP進程用戶讀寫權限;檢查是否有其他進程打開文件 |
| Data type mismatch in criteria expression | SQL參數類型不匹配 | 字符串值加單引號,日期用 #YYYY-MM-DD# 格式 |
| Operation must use an updateable query | 查詢結果集不可更新 | 避免使用JOIN、DISTINCT、GROUP BY等導致只讀記錄集 |