XML 是資料交換常用格式,PHP 提供多種解析與生成方式。本文詳細比較 DOM、SimpleXML、XMLReader、XMLWriter 的適用場景、記憶體特性與程式碼範例,協助選擇最適合的工具。
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="bk101" category="programming">
<title>PHP 8 實戰開發</title>
<author>王小明</author>
<publisher>技術出版社</publisher>
<price currency="TWD">680</price>
<publish_date>2023-05-15</publish_date>
<description>涵蓋 PHP 8 新特性、效能優化與最佳實踐</description>
</book>
<book id="bk102" category="database">
<title>MySQL 8 管理指南</title>
<author>李大華</author>
<publisher>資料庫雜誌社</publisher>
<price currency="TWD">720</price>
<publish_date>2023-03-20</publish_date>
<description>從安裝調校到高可用架構</description>
</book>
</catalog>
特性:載入整份文件到記憶體、支援 XPath、可隨意修改結構、適合中小型 XML(< 50MB)
<?php
$dom = new DOMDocument();
$dom->load('books.xml');
$xpath = new DOMXPath($dom);
// 查詢所有 programming 類別的書
$books = $xpath->query('//book[@category="programming"]');
foreach ($books as $book) {
$title = $xpath->evaluate('string(title)', $book);
$price = $xpath->evaluate('string(price)', $book);
$currency = $xpath->evaluate('string(price/@currency)', $book);
echo "{$title} - {$price} {$currency}\n";
}
// 取得特定 ID 的書
$book = $dom->getElementById('bk101'); // 需先設定 ID 屬性
if (!$book) {
$book = $xpath->query('//book[@id="bk101"]')->item(0);
}
?>
<?php
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true; // 美化輸出
$catalog = $dom->createElement('catalog');
$dom->appendChild($catalog);
$book = $dom->createElement('book');
$book->setAttribute('id', 'bk103');
$book->setAttribute('category', 'web');
$catalog->appendChild($book);
$title = $dom->createElement('title', 'Laravel 實戰');
$book->appendChild($title);
$author = $dom->createElement('author', '陳開發');
$book->appendChild($author);
$price = $dom->createElement('price', '850');
$price->setAttribute('currency', 'TWD');
$book->appendChild($price);
// 儲存
$dom->save('new_book.xml');
// 或輸出字串
$xmlString = $dom->saveXML();
?>
特性:語法直覺、自動轉換為物件/陣列、唯讀導向、不支援複雜修改、記憶體佔用略低於 DOM
<?php
$xml = simplexml_load_file('books.xml');
// 存取屬性
foreach ($xml->book as $book) {
echo "ID: {$book['id']}\n";
echo "分類: {$book['category']}\n";
echo "標題: {$book->title}\n";
echo "價格: {$book->price} {$book->price['currency']}\n";
// XPath 查詢
$programmingBooks = $xml->xpath('//book[@category="programming"]');
}
// 轉陣列(含屬性)
function simpleXmlToArray(SimpleXMLElement $xml): array {
$json = json_encode($xml);
return json_decode($json, true);
}
$array = simpleXmlToArray($xml);
?>
<?php
$xml = new SimpleXMLElement('<catalog/>');
$book = $xml->addChild('book');
$book->addAttribute('id', 'bk103');
$book->addAttribute('category', 'web');
$book->addChild('title', 'Laravel 實戰');
$book->addChild('author', '陳開發');
$price = $book->addChild('price', '850');
$price->addAttribute('currency', 'TWD');
$xml->asXML('simple_book.xml');
?>
特性:游標式前向讀取、不載入整份文件、適合大型 XML(GB 級)、唯讀、速度快
<?php
$reader = new XMLReader();
$reader->open('large_catalog.xml');
$books = [];
$currentBook = null;
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) {
if ($reader->name === 'book') {
$currentBook = [
'id' => $reader->getAttribute('id'),
'category' => $reader->getAttribute('category'),
'title' => '',
'author' => '',
'price' => '',
'currency' => ''
];
} elseif ($currentBook && in_array($reader->name, ['title', 'author', 'price'])) {
$reader->read(); // 移到文字節點
$currentBook[$reader->name] = $reader->value;
if ($reader->name === 'price') {
$currentBook['currency'] = $reader->getAttribute('currency');
}
}
} elseif ($reader->nodeType === XMLReader::END_ELEMENT && $reader->name === 'book') {
$books[] = $currentBook;
$currentBook = null;
}
}
$reader->close();
// $books 現在包含所有書的資料,記憶體只佔用陣列大小
?>
特性:記憶體效率高、適合產生大型 XML 檔案、只寫不讀
<?php
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString(' ');
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('catalog');
for ($i = 1; $i <= 10000; $i++) {
$writer->startElement('book');
$writer->writeAttribute('id', "bk{$i}");
$writer->writeAttribute('category', 'generated');
$writer->writeElement('title', "Generated Book {$i}");
$writer->writeElement('author', "Author {$i}");
$writer->writeElement('price', rand(100, 1000));
$writer->endElement(); // book
// 定期輸出避免記憶體累積
if ($i % 1000 === 0) {
file_put_contents('generated_books.xml', $writer->outputMemory(true), FILE_APPEND);
}
}
$writer->endElement(); // catalog
$writer->endDocument();
file_put_contents('generated_books.xml', $writer->outputMemory(true), FILE_APPEND);
?>
| 解析器 | 記憶體模式 | 適合大小 | 讀取速度 | 寫入支援 | XPath |
|---|---|---|---|---|---|
| DOMDocument | 樹狀結構全載入 | < 50MB | 中 | 完整 | 完整支援 |
| SimpleXML | 樹狀結構全載入 | < 30MB | 快 | 基本 | 支援 |
| XMLReader | 流式(常數記憶體) | 無上限 | 最快 | 無 | 無 |
| XMLWriter | 流式(常數記憶體) | 無上限 | 最快 | 完整 | 無 |
<?php
$xml = simplexml_load_file('namespaced.xml');
// 註冊命名空間
$xml->registerXPathNamespace('ns', 'https://example.com/ns');
$items = $xml->xpath('//ns:item');
// DOMDocument
$dom = new DOMDocument();
$dom->load('namespaced.xml');
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('ns', 'https://example.com/ns');
$items = $xpath->query('//ns:item');
?>
<?php
// 停用外部實體載入(防止 XXE 攻擊)
libxml_disable_entity_loader(true);
// 載入時忽略警告
$dom = new DOMDocument();
$dom->loadXML($xmlString, LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NONET);
// 檢查錯誤
if ($dom->schemaValidate('schema.xsd')) {
echo "驗證通過";
} else {
foreach (libxml_get_errors() as $error) {
error_log("XML 驗證錯誤:{$error->message} at line {$error->line}");
}
libxml_clear_errors();
}
?>
掌握這四種工具的特性與適用場景,能讓 PHP 處理 XML 任務事半功倍。現代專案建議:配置檔用 SimpleXML,資料匯入用 XMLReader,報表匯出用 XMLWriter,複雜操作用 DOMDocument。