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

PHP 操作 COM 組件實戰:訪問 Access 數據庫與自動化 Word/Excel

     編輯:PHP 相關知識

前言

PHP 的 COM 擴展(php_com_dotnet.dll)允許在 Windows 平台上調用 COM 組件,實現對 Access 數據庫、Office 應用、系統組件(WMI、ADSI、Scripting.FileSystemObject 等)的原生訪問。雖然跨平台性為零,但在 Windows 內網、遷移舊 VB6/ASP 系統、辦公自動化、報表生成等場景極具價值。本文系統講解 ADO 訪問 Access、Word 自動化、Excel 報表、錯誤處理、性能優化及安全邊界。

環境準備

  1. Windows Server / Windows 10/11 (x64)
  2. PHP 7.4+ / 8.x (TS 線程安全版,需 Apache mod_php 或 IIS FastCGI)
  3. 啟用擴展:extension=com_dotnet (php.ini)
  4. 安裝目標 COM 組件:Microsoft Access Database Engine (ACE.OLEDB)、Microsoft Office (Word/Excel)
  5. DCOM 權限配置:dcomcnfg → 組件服務 → 計算機 → 我的電腦 → 屬性 → COM 安全 → 啟動和激活權限/訪問權限 → 添加 IIS_IUSRS / php-fpm 用戶

ADO 連接 Access 數據庫

connStr = "Provider=Microsoft.ACE.OLEDB.16.0;Data Source={$dbPath};Persist Security Info=False;";
    }

    public function connect(): void {
        $this->conn = new COM('ADODB.Connection');
        $this->conn->ConnectionString = $this->connStr;
        $this->conn->Open();
    }

    public function query(string $sql, array $params = []): array {
        $rs = $this->conn->Execute($sql, $affectedRows, 1); // 1 = adCmdText
        if (!$rs) throw new RuntimeException("查詢執行失敗: {$sql}");

        $results = [];
        while (!$rs->EOF) {
            $row = [];
            for ($i = 0; $i < $rs->Fields->Count; $i++) {
                $field = $rs->Fields($i);
                $row[$field->Name] = $field->Value;
            }
            $results[] = $row;
            $rs->MoveNext();
        }
        $rs->Close();
        return $results;
    }

    public function execute(string $sql, array $params = []): int {
        $this->conn->Execute($sql, $affectedRows, 1);
        return $affectedRows;
    }

    public function beginTransaction(): void {
        $this->conn->BeginTrans();
    }

    public function commit(): void {
        $this->conn->CommitTrans();
    }

    public function rollback(): void {
        $this->conn->RollbackTrans();
    }

    public function __destruct() {
        if ($this->conn && $this->conn->State) {
            $this->conn->Close();
        }
    }
}

// 使用示例
$db = new AccessDatabase('D:\Data\business.accdb');
$db->connect();
$users = $db->query("SELECT id, username, email FROM users WHERE active = ?", [true]);
foreach ($users as $u) {
    echo "{$u['id']}: {$u['username']} <{$u['email']}>\n";
}

關鍵注意點

  • 32/64 位匹配:PHP、Access Database Engine、Office 必須同位數(均 64 位或均 32 位),否則 Class not registered。
  • 參數化查詢:ADODB.Command + Parameters 集合防注入,比字符串拼接安全。
  • 游標類型:默認前向只讀,需可滾動/可更新時設置 CursorLocation = 3 (adUseClient)、CursorType = 3 (adOpenStatic)、LockType = 3 (adLockOptimistic)。
  • 資源釋放:Recordset、Connection 必須顯式 Close(),否則導致 .ldb 鎖文件殘留、連接泄漏。

Word 自動化:文檔生成與轉換

word = new COM('Word.Application') or die('無法啟動 Word');
        $this->word->Visible = $visible;
        $this->word->DisplayAlerts = 0; // 靜默模式
    }

    public function createFromTemplate(string $templatePath, array $bookmarks, string $outputPath, string $format = 'pdf'): bool {
        try {
            $doc = $this->word->Documents->Open($templatePath, false, true); // 唯讀打開模板
            foreach ($bookmarks as $name => $text) {
                if ($doc->Bookmarks->Exists($name)) {
                    $doc->Bookmarks($name)->Range->Text = $text;
                }
            }
            $formatMap = ['pdf' => 17, 'docx' => 16, 'doc' => 0, 'rtf' => 6, 'html' => 8];
            $doc->SaveAs($outputPath, $formatMap[$format] ?? 17);
            $doc->Close(false);
            return true;
        } catch (com_exception $e) {
            error_log("Word 自動化失敗: {$e->getMessage()}");
            return false;
        }
    }

    public function convertToPdf(string $inputPath, string $outputPath): bool {
        try {
            $doc = $this->word->Documents->Open($inputPath, false, true);
            $doc->SaveAs($outputPath, 17); // wdFormatPDF = 17
            $doc->Close(false);
            return true;
        } catch (com_exception $e) {
            return false;
        }
    }

    public function __destruct() {
        if ($this->word) {
            $this->word->Quit(false);
            unset($this->word);
        }
    }
}

// 使用示例
$word = new WordAutomation();
$bookmarks = ['ClientName' => '張三', 'Amount' => '¥10,000.00', 'Date' => date('Y-m-d')];
$word->createFromTemplate('D:\Templates\contract.dotx', $bookmarks, 'D:\Output\contract_123.pdf');

Excel 自動化:報表導出與圖表生成

excel = new COM('Excel.Application') or die('無法啟動 Excel');
        $this->excel->Visible = $visible;
        $this->excel->DisplayAlerts = false;
        $this->excel->ScreenUpdating = false; // 關閉屏幕刷新,大幅提速
    }

    public function exportReport(string $templatePath, array $data, string $outputPath): bool {
        try {
            $wb = $this->excel->Workbooks->Open($templatePath);
            $ws = $wb->Worksheets(1); // 第一個工作表

            // 寫入標題行
            $headers = array_keys($data[0] ?? []);
            foreach ($headers as $col => $header) {
                $ws->Cells(1, $col + 1)->Value = $header;
            }

            // 批量寫入數據 (陣列一次性賦值,比逐單元格快 100x+)
            $rowCount = count($data);
            $colCount = count($headers);
            if ($rowCount > 0) {
                $values = [];
                foreach ($data as $row) {
                    $values[] = array_values($row);
                }
                $range = $ws->Range($ws->Cells(2, 1), $ws->Cells($rowCount + 1, $colCount));
                $range->Value = $values;
            }

            // 自動調整列寬
            $ws->Columns->AutoFit();

            $wb->SaveAs($outputPath, 51); // xlOpenXMLWorkbook = 51 (.xlsx)
            $wb->Close(false);
            return true;
        } catch (com_exception $e) {
            error_log("Excel 導出失敗: {$e->getMessage()}");
            return false;
        }
    }

    public function __destruct() {
        if ($this->excel) {
            $this->excel->Quit();
            unset($this->excel);
        }
    }
}

性能與穩定性優化鐵律

問題原因解決
極慢 (秒級/操作)COM 跨進程調用開銷大、逐屬性/方法調用封送批量操作 (Range.Value = 陣列)、關閉 ScreenUpdating/DisplayAlerts、最小化跨邊界調用
內存洩漏/進程殘留COM 物件未正確 Release、PHP 異常中斷未清理__destruct 中 Quit()、try/finally 確保關閉、定期重啟 PHP-FPM/IIS 應用池
並發失敗Office 應用單實例、非線程安全、文件鎖衝突串行隊列處理 (Redis/RabbitMQ + 單消費者)、文件唯一命名、超時監控
權限拒絕DCOM 啟動身份、文件系統 ACL、受保護視圖dcomcnfg 設置啟動用戶、輸出目錄授予寫入權、關閉受保護視圖

現代替代方案:無需 Office 安裝

需求推薦庫 (Composer)特點
讀寫 .xlsx/.xlsPhpSpreadsheet純 PHP、功能全、內存佔用大、大文件需分塊
高性能 .xlsx 寫入Box/Spout流式寫入、常數內存、速度快、無圖表/樞紐表
讀寫 .docxPhpOffice/PhpWord純 PHP、模板引擎、轉 PDF 需 LibreOffice
Access 數據庫PDO_ODBC + ACE Driver標準 PDO 接口、跨平台 (Linux 需 mdbtools)、無 COM 依賴
文檔轉 PDFLibreOffice Headlesslibreoffice --headless --convert-to pdf input.docx、完美還原、無 Office 授權

結語

PHP COM 橋接了 Web 應用與 Windows 生態,在辦公自動化、遷移舊系統、報表生成中不可替代。但COM 本質是跨進程 RPC,開銷大、脆弱、難併發。核心原則:能用純 PHP 庫解決的絕不用 COM;必須用 COM 時,隔離在獨立 Worker 進程,通過隊列串行處理,嚴格資源清理與超時監控。新架構設計階段,優先選擇 PhpSpreadsheet、LibreOffice Headless、PDO_ODBC 等無平台鎖定方案。

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