simplexml_load_string() 是 PHP 處理 XML 最便捷的工具,但其返回的 SimpleXMLElement 物件行為與普通陣列/物件截然不同,極易踩坑。本文通過實例深度解析「偽陣列」特性、屬性訪問、命名空間、編碼處理、大文件流式解析等進階主題。
<?php
$xml = <<
hello
world
XML;
$data = simplexml_load_string($xml);
// print_r 顯示 foo 是陣列,包含兩個元素
print_r($data);
/* 輸出:
SimpleXMLElement Object
(
[foo] => Array
(
[0] => SimpleXMLElement Object ([bar] => hello)
[1] => SimpleXMLElement Object ([bar] => world)
)
)
*/
// 但直接訪問 $data->foo 只返回第一個元素!
echo $data->foo->bar; // hello (不是陣列!)
// 正確遍歷方式:foreach
foreach ($data->foo as $item) {
echo $item->bar . "\n"; // hello / world
}
// 或使用 children()
foreach ($data->children() as $child) {
echo $child->bar . "\n";
}
?>
結論:SimpleXMLElement 實現了 Traversable、Countable、ArrayAccess,但**不是陣列**。print_r/var_dump 為了可讀性將其「偽裝」為陣列,實際訪問需用 foreach、[0] 索引或 ->children()。
<?php
$xml = <<
你好
Hello
你好
World
世界
XML;
$x = simplexml_load_string($xml);
// 訪問屬性:$x['屬性名'] 或 $x->attributes()
echo $x['num']; // 219 (字串)
echo $x->attributes()->num; // 同樣
echo (int)$x['num']; // 219 (整數)
// 遍歷所有屬性
foreach ($x->attributes() as $name => $val) {
echo "$name = $val\n";
}
?>
<?php
$xml = <<
Value
XML;
$x = simplexml_load_string($xml);
// 註冊命名空間前綴
$x->registerXPathNamespace('p', 'https://example.com/ns');
// XPath 查詢
$items = $x->xpath('//p:item');
foreach ($items as $item) {
echo $item . "\n"; // Value
echo $item['id']; // 1
}
?>
<?php
// 錯誤示範:GBK 字串直接餵給 simplexml_load_string
$gbkXml = iconv('UTF-8', 'GBK', $utf8Xml);
$data = simplexml_load_string($gbkXml); // 解析錯誤或亂碼
// 正確:統一轉為 UTF-8 再解析
$utf8Xml = iconv('GBK', 'UTF-8', $gbkXml);
$data = simplexml_load_string($utf8Xml);
// 輸出時也需注意編碼
header('Content-Type: text/xml; charset=UTF-8');
echo $data->asXML(); // 返回 UTF-8 XML 字串
?>
<?php
// 100MB+ XML 僅用幾 MB 記憶體
$reader = new XMLReader();
$reader->open('huge.xml');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->localName === 'item') {
// 只展開當前節點為 SimpleXMLElement
$node = simplexml_import_dom($reader->expand());
// 處理 $node->id, $node->title ...
process_item($node);
// 釋放節點 DOM
$reader->next('item');
}
}
$reader->close();
?>
<?php
function parse_wechat_xml(string $xml): array {
// 1. 禁用外部實體防止 XXE 攻擊
libxml_disable_entity_loader(true);
// 2. 解析
$sxe = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($sxe === false) {
throw new Exception('XML 解析失敗: ' . libxml_get_last_error()->message);
}
// 3. 轉為純陣列 (遞歸)
return json_decode(json_encode($sxe), true);
// 注意:json_encode 會丟失屬性 @attributes、CDATA 標記、命名空間
// 嚴格場景請手寫遞歸轉換函數
}
// 使用
$xml = file_get_contents('php://input'); // 微信回調原始 XML
$data = parse_wechat_xml($xml);
echo $data['MsgType']; // text/event/image...
?>
| 方法 | 記憶體峰值 | 耗時 | 適用場景 |
|---|---|---|---|
| simplexml_load_string | ~45 MB | 0.8s | <5MB、結構簡單、需隨機訪問 |
| simplexml_load_file | ~45 MB | 0.8s | 同上,直接讀文件 |
| XMLReader + simplexml_import_dom | ~3 MB | 1.2s | 大文件、流式處理、單次遍歷 |
| SAX (xml_parser_create) | ~2 MB | 1.5s | 極大文件、C 級性能、回調地獄 |
iconv/mb_convert_encoding 確保 UTF-8。libxml_disable_entity_loader(true) 防 XXE。LIBXML_NOCDATA 將 CDATA 直接作為文本節點,避免 SimpleXMLElement 物件包裝。json_decode(json_encode($sxe), true) 簡便但有損;嚴格場景寫遞歸函數保留屬性/命名空間。XMLReader 流式解析。