| 维度 | XML | JSON |
|---|---|---|
| 体积 | 冗长(标签重复) | 紧凑 |
| 解析速度 | 较慢(DOM/SAX) | 极快(原生 json_decode) |
| 类型系统 | 仅文本,需 Schema 约束 | 原生数字/布尔/数组/对象 |
| 命名空间/混合内容 | 原生支持 | 需约定俗成 |
| 流式处理 | XMLReader (pull parser) | 无标准流式解析器 |
| 适用场景 | 企业集成、SOAP、配置文件、文档标记 | REST API、前后端交互、配置、日志 |
<?php
function generateStudentXml(array $students): string
{
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true; // 缩进美化
$dom->preserveWhiteSpace = false;
$root = $dom->createElement('students');
$dom->appendChild($root);
foreach ($students as $stu) {
$node = $dom->createElement('student');
$node->setAttribute('id', $stu['id']);
// 简单元素
foreach (['name', 'sex', 'age'] as $field) {
$child = $dom->createElement($field, $stu[$field]);
$node->appendChild($child);
}
// CDATA 包含 HTML/特殊字符
if (!empty($stu['bio'])) {
$bio = $dom->createElement('bio');
$bio->appendChild($dom->createCDATASection($stu['bio']));
$node->appendChild($bio);
}
$root->appendChild($node);
}
return $dom->saveXML();
}
// 数据源
$students = [
['id' => 1, 'name' => '张三', 'sex' => '男', 'age' => 20, 'bio' => '喜欢<编程> & 游戏'],
['id' => 2, 'name' => '李四', 'sex' => '女', 'age' => 19, 'bio' => ''],
];
header('Content-Type: application/xml; charset=UTF-8');
echo generateStudentXml($students);
输出示例:
<?xml version="1.0" encoding="UTF-8"?>
张三
男
20
& 游戏]]>
李四
女
19
<?php
function parseLargeXml(string $urlOrPath): \Generator
{
$reader = new XMLReader();
if (!$reader->open($urlOrPath)) {
throw new \RuntimeException("Cannot open XML: $urlOrPath");
}
try {
while ($reader->read()) {
// 只关心 元素开始
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'student') {
// 读取完整 子树为 DOMNode
$node = $reader->expand(); // 返回 DOMElement
$dom = new DOMDocument();
$studentNode = $dom->importNode($node, true);
yield [
'id' => $studentNode->getAttribute('id'),
'name' => $studentNode->getElementsByTagName('name')->item(0)?->textContent,
'sex' => $studentNode->getElementsByTagName('sex')->item(0)?->textContent,
'age' => (int)$studentNode->getElementsByTagName('age')->item(0)?->textContent,
'bio' => $studentNode->getElementsByTagName('bio')->item(0)?->textContent,
];
}
}
} finally {
$reader->close();
}
}
// 使用:逐个处理,内存仅保留当前节点
foreach (parseLargeXml('https://example.com/students.xml') as $stu) {
// 入库、转 JSON、推送队列...
echo "{$stu['name']} ({$stu['age']})\n";
}
XMLReader::expand() 将当前节点及子树导入 DOM,便于 XPath/DOM 方法提取,但会临时构建子树 DOM,超大单节点(>10MB)仍需注意。
<?php
$xml = simplexml_load_file('students.xml');
// 或 simplexml_load_string($xmlStr)
foreach ($xml->student as $stu) {
echo (string)$stu->name; // 强制转 string
echo (string)$stu['id']; // 属性访问
// 命名空间:$stu->children('ns', true)->field
}
<?php
$xml = <<
Value
XML;
$dom = new DOMDocument();
$dom->loadXML($xml);
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('foo', 'https://example.com/foo');
$items = $xpath->query('//foo:item');
foreach ($items as $item) {
echo $item->getAttribute('id') . ': ' . $item->textContent;
}
<?php
// XML -> JSON
function xmlToJson(string $xml): string
{
$dom = new DOMDocument();
$dom->loadXML($xml);
// 简易转换:属性变 @attr,文本变 #text
$json = json_encode(domToArray($dom->documentElement), JSON_UNESCAPED_UNICODE);
return $json;
}
function domToArray(DOMElement $el): array
{
$arr = ['@attributes' => []];
foreach ($el->attributes as $attr) {
$arr['@attributes'][$attr->name] = $attr->value;
}
foreach ($el->childNodes as $child) {
if ($child->nodeType === XML_TEXT_NODE) {
$arr['#text'] = $child->textContent;
} elseif ($child->nodeType === XML_ELEMENT_NODE) {
$key = $child->tagName;
$value = domToArray($child);
if (isset($arr[$key])) {
if (!isset($arr[$key][0])) $arr[$key] = [$arr[$key]];
$arr[$key][] = $value;
} else {
$arr[$key] = $value;
}
}
}
return $arr;
}
// JSON -> XML (简易)
function jsonToXml(string $json, string $rootName = 'root'): string
{
$data = json_decode($json, true);
$dom = new DOMDocument('1.0', 'UTF-8');
$root = $dom->createElement($rootName);
$dom->appendChild($root);
arrayToDom($data, $root, $dom);
return $dom->saveXML();
}
function arrayToDom(array $data, DOMElement $parent, DOMDocument $dom): void
{
foreach ($data as $key => $value) {
if ($key === '@attributes') {
foreach ($value as $attr => $val) {
$parent->setAttribute($attr, $val);
}
} elseif ($key === '#text') {
$parent->appendChild($dom->createTextNode($value));
} elseif (is_array($value) && isset($value[0])) {
foreach ($value as $item) {
$child = $dom->createElement($key);
$parent->appendChild($child);
arrayToDom($item, $child, $dom);
}
} else {
$child = $dom->createElement($key);
$parent->appendChild($child);
arrayToDom($value, $child, $dom);
}
}
}
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 生成小配置/数据交换 | JSON (json_encode) | 原生、快、体积小 |
| 生成标准 XML 接口(对外) | DOMDocument | 规范、支持 CDATA/命名空间/Schema 验证 |
| 解析小 XML(<5MB) | SimpleXML | 代码极简、语法糖丰富 |
| 解析大 XML(>50MB) | XMLReader + expand() | 流式、内存恒定、支持 XPath 片段查询 |
| 高性能解析固定结构 | XMLReader 纯游标模式 | 无 DOM 开销,仅节点类型/名称/值判断 |
PHP 操作 XML 核心三件套:DOMDocument 生成、XMLReader 流式解析大文件、SimpleXML 便捷解析小文件。现代 API 优先选 JSON,仅在对接遗留系统、SOAP、标准化文档交换时使用 XML。掌握 XMLReader::expand() 与 DOMXPath 配合,能优雅解决「大文件+复杂查询」的矛盾。