為什麼選 XMLWriter?
- **流式寫入**:不構建完整 DOM 樹,內存佔用極低,適合大數據導出。
- **自動轉義**:自動處理特殊字符 (
< > & " ') 和編碼聲明。
- **性能優異**:C 底層實現,比手動拼接字符串或 DOMDocument 快數倍。
- **標準擴展**:PHP 5.1.2+ 內置,無需額外安裝。
通用轉換類 A2Xml
<?php
class A2Xml {
private $version = '1.0';
private $encoding = 'UTF-8';
private $rootName = 'root';
private $writer;
public function __construct(string $rootName = 'root', string $encoding = 'UTF-8', string $version = '1.0') {
$this->rootName = $rootName;
$this->encoding = $encoding;
$this->version = $version;
$this->writer = new XMLWriter();
}
/**
* 數組轉 XML 字符串
* @param array $data 待轉換數組 (支持多維嵌套)
* @param bool $pretty 是否格式化縮進 (默認 true)
* @return string XML 字符串
*/
public function toXml(array $data, bool $pretty = true): string {
$this->writer->openMemory();
$this->writer->setIndent($pretty);
$this->writer->setIndentString(' ');
$this->writer->startDocument($this->version, $this->encoding);
$this->writer->startElement($this->rootName);
$this->writeNode($data);
$this->writer->endElement(); // root
$this->writer->endDocument();
return $this->writer->outputMemory(true);
}
private function writeNode(array $data): void {
foreach ($data as $key => $value) {
// 鍵名合法化:XML 標簽不能以數字/特殊字符開頭
$tag = preg_replace('/^[^a-zA-Z_]/', '_$0', $key);
$tag = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $tag);
if (is_array($value)) {
$this->writer->startElement($tag);
$this->writeNode($value); // 遞歸
$this->writer->endElement();
} elseif (is_bool($value)) {
$this->writer->writeElement($tag, $value ? 'true' : 'false');
} elseif ($value === null) {
$this->writer->writeElement($tag, ''); // 或寫 xsi:nil="true"
} else {
$this->writer->writeElement($tag, (string)$value);
}
}
}
}
?>
使用示例
<?php
$data = [
'order' => [
'id' => 1001,
'customer' => ['name' => '張三', 'email' => 'zhang@example.com'],
'items' => [
['sku' => 'A001', 'qty' => 2, 'price' => 99.00],
['sku' => 'A002', 'qty' => 1, 'price' => 149.00],
],
'paid' => true,
'note' => null,
]
];
$xml = new A2Xml('order');
echo $xml->toXml($data);
/* 輸出:
1001
張三
zhang@example.com
-
A001
2
99
-
A002
1
149
true
*/
?>
關鍵細節處理
| 問題 | 解決方案 |
| 數字鍵名 (0,1,2...) | XML 標簽不能以數字開頭,自動加前綴 _0、_1 或統一用 item (需檢測連續數字索引) |
| 特殊字符鍵名 | 正則替換非法字符為下劃線 |
| 布爾值 | 轉為 true/false 字符串 |
| NULL 值 | 輸出空元素或 xsi:nil="true" |
| CDATA 區塊 | 含 HTML/腳本內容時調用 writeCData(),可添加 @cdata 鍵名約定自動識別 |
進階:數字索引自動包裹為
private function writeNode(array $data): void {
$isList = array_keys($data) === range(0, count($data)-1);
foreach ($data as $key => $value) {
$tag = $isList ? 'item' : $this->sanitizeTag($key);
// ... 同前
}
}
private function sanitizeTag(string $key): string {
return preg_replace('/[^a-zA-Z0-9_.-]/', '_', ltrim($key, '0-9'));
}
對比:手動拼接 vs DOMDocument vs XMLWriter
| 方案 | 內存 | 速度 | 安全性 | 推薦度 |
| 字符串拼接 | 高 (全量) | 快 (小數據) | 極低 (易注入、編碼錯誤) | ❌ 禁用 |
| DOMDocument | 高 (構建樹) | 中 | 高 | ✅ 小數據、需後續操作 DOM |
| XMLWriter | **極低 (流式)** | **最快** | 高 (自動轉義) | ✅✅✅ **首選** |