SimpleXML
SimpleXML 是 PHP 中操作 XML 最简便的扩展。它将 XML 文档转换为易于使用的对象,可以通过属性访问来读写 XML 元素和属性。虽然不如 DOM 功能全面,但对于大多数 XML 操作场景来说足够简洁高效。
前置知识
阅读本节前,建议先了解:DOM 操作
基础概念
SimpleXML 的优势
- 简洁直观:使用对象属性语法访问 XML
- XPath 支持:内置 XPath 查询
- 易于读写:修改 XML 和创建 XML 都很方便
- 自动迭代:多个同名元素可通过 foreach 遍历
SimpleXML 的局限
- 无法直接操作文档类型声明(DTD)
- 不支持某些高级 DOM 特性
- 内存占用与 DOM 相当(不适合超大文件)
加载 XML
从字符串加载
php
<?php
declare(strict_types=1);
$xmlString = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<library>
<book id="1" category="fiction">
<title>Harry Potter</title>
<author>J.K. Rowling</author>
<year>2005</year>
<price currency="USD">29.99</price>
<tags>
<tag>fantasy</tag>
<tag>adventure</tag>
</tags>
</book>
<book id="2" category="programming">
<title>Clean Code</title>
<author>Robert C. Martin</author>
<year>2008</year>
<price currency="USD">39.99</price>
<tags>
<tag>programming</tag>
<tag>craftsmanship</tag>
</tags>
</book>
<book id="3" category="programming">
<title>Design Patterns</title>
<author>Gang of Four</author>
<year>1994</year>
<price currency="USD">49.99</price>
</book>
</library>
XML;
$library = simplexml_load_string($xmlString);
if ($library === false) {
throw new RuntimeException("XML 解析失败");
}
echo "根元素: " . $library->getName() . PHP_EOL;从文件加载
php
<?php
declare(strict_types=1);
// simplexml_load_file 返回 SimpleXMLElement 或 false
$library = simplexml_load_file('library.xml');
if ($library === false) {
throw new RuntimeException("加载 XML 文件失败");
}
// 也可以使用 libxml 选项
$library = simplexml_load_file('library.xml', 'SimpleXMLElement', LIBXML_NOCDATA);使用 SimpleXMLElement 构造函数
php
<?php
declare(strict_types=1);
// 从文件
$element = new SimpleXMLElement('library.xml', 0, true);
// 从字符串
$element = new SimpleXMLElement($xmlString);读取 XML
访问元素
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// 访问第一个 book
$firstBook = $library->book[0];
echo "书名: " . $firstBook->title . PHP_EOL; // Harry Potter
echo "作者: " . $firstBook->author . PHP_EOL; // J.K. Rowling
echo "年份: " . $firstBook->year . PHP_EOL; // 2005
// 访问第二个 book
echo "第二本书: " . $library->book[1]->title . PHP_EOL; // Clean Code
// 遍历所有书籍
foreach ($library->book as $book) {
echo "{$book['id']}: {$book->title} ({$book->author})" . PHP_EOL;
}
// 访问属性
foreach ($library->book as $book) {
$id = $book['id']; // 字符串形式
$category = $book['category'];
echo "ID: {$id}, 分类: {$category}" . PHP_EOL;
// 获取价格货币属性
$currency = $book->price['currency'];
$price = (string) $book->price;
echo "价格: {$price} {$currency}" . PHP_EOL;
}访问深层嵌套元素
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// 链式访问
$firstTag = $library->book[0]->tags->tag[0];
echo "第一个标签: {$firstTag}" . PHP_EOL; // fantasy
// 遍历标签
foreach ($library->book[0]->tags->tag as $tag) {
echo "标签: {$tag}" . PHP_EOL;
}类型转换
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// SimpleXMLElement 默认是对象
$title = $library->book[0]->title;
// 转为字符串
$titleString = (string) $title;
// 转为整数
$year = (int) $library->book[0]->year;
// 转为浮点数
$price = (float) $library->book[0]->price;
// 转为数组
$bookArray = (array) $library->book[0];
print_r($bookArray);
// 转为 JSON
$json = json_encode($library->book[0], JSON_PRETTY_PRINT);
echo $json;
// 导出为 DOMDocument
$dom = dom_import_simplexml($library);
$domDoc = $dom->ownerDocument;XPath 查询
基本查询
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// 使用 xpath() 方法
$books = $library->xpath('//book');
foreach ($books as $book) {
echo "{$book->title}: {$book->price}" . PHP_EOL;
}
// 条件查询:价格大于 30 的书
$expensiveBooks = $library->xpath('//book[price > 30]');
foreach ($expensiveBooks as $book) {
echo "高价书: {$book->title} ({$book->price})" . PHP_EOL;
}
// 按属性查询
$programmingBooks = $library->xpath('//book[@category="programming"]');
foreach ($programmingBooks as $book) {
echo "编程书: {$book->title}" . PHP_EOL;
}
// 获取所有标签
$allTags = $library->xpath('//tag');
foreach ($allTags as $tag) {
echo "标签: {$tag}" . PHP_EOL;
}
// 统计
$count = $library->xpath('count(//book)');
echo "书籍总数: {$count[0]}" . PHP_EOL;注册命名空间
php
<?php
declare(strict_types=1);
$nsXml = <<<XML
<?xml version="1.0"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>Example Feed</title>
<atom:link href="https://example.com/feed.xml" rel="self"/>
<item>
<title>文章标题</title>
<link>https://example.com/post1</link>
</item>
</channel>
</rss>
XML;
$rss = simplexml_load_string($nsXml);
// 注册命名空间后才能查询
$rss->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
$atomLink = $rss->xpath('//atom:link/@href');
echo "Feed URL: " . (string) $atomLink[0] . PHP_EOL;修改 XML
修改元素和属性
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// 修改元素值
$library->book[0]->title = 'Harry Potter and the Philosopher\'s Stone';
$library->book[0]->year = 1997;
$library->book[0]->price = 24.99;
// 修改属性
$library->book[0]['category'] = 'fantasy';
// 添加属性
$library->book[0]->addAttribute('isbn', '978-0747532699');
// 删除属性
unset($library->book[0]['id']);
// 输出修改后的 XML
echo $library->asXML();添加节点
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// 添加新书
$newBook = $library->addChild('book');
$newBook->addAttribute('category', 'science');
$newBook->addAttribute('id', '4');
$newBook->addChild('title', 'A Brief History of Time');
$newBook->addChild('author', 'Stephen Hawking');
$newBook->addChild('year', '1988');
$newBook->addChild('price', '25.00');
// 添加带属性的子元素
$priceNode = $newBook->price;
$priceNode->addAttribute('currency', 'USD');
// 添加标签
$tags = $newBook->addChild('tags');
$tags->addChild('tag', 'science');
$tags->addChild('tag', 'physics');
echo $library->asXML();删除节点
php
<?php
declare(strict_types=1);
$library = simplexml_load_string($xmlString);
// 删除特定书籍(使用 XPath 或数组索引)
$books = $library->book;
$indexToRemove = 1;
// SimpleXML 没有直接的删除方法,需要用 DOM
foreach ($library->xpath('//book[@id="2"]') as $book) {
$dom = dom_import_simplexml($book);
$dom->parentNode->removeChild($dom);
}
// 或者使用 unset 删除索引节点
unset($library->book[0]);
echo $library->asXML();创建 XML
创建 XML 文档
php
<?php
declare(strict_types=1);
// 创建根元素
$library = new SimpleXMLElement('<library></library>');
// 添加书籍
$book1 = $library->addChild('book');
$book1->addAttribute('id', '1');
$book1->addAttribute('category', 'fiction');
$book1->addChild('title', 'The Great Gatsby');
$book1->addChild('author', 'F. Scott Fitzgerald');
$book1->addChild('year', '1925');
$price1 = $book1->addChild('price', '12.99');
$price1->addAttribute('currency', 'USD');
$book2 = $library->addChild('book');
$book2->addAttribute('id', '2');
$book2->addAttribute('category', 'programming');
$book2->addChild('title', 'PHP 8 Explained');
$book2->addChild('author', 'Author Name');
$book2->addChild('year', '2024');
$price2 = $book2->addChild('price', '45.00');
$price2->addAttribute('currency', 'USD');
// 设置 XML 声明
$dom = dom_import_simplexml($library);
$domDoc = $dom->ownerDocument;
$domDoc->version = '1.0';
$domDoc->encoding = 'UTF-8';
// 格式化输出
$domDoc->formatOutput = true;
echo $domDoc->saveXML($domDoc->documentElement);从数组创建 XML
php
<?php
declare(strict_types=1);
/**
* 将数组转换为 SimpleXML
*/
function arrayToXml(array $data, SimpleXMLElement $xml): void
{
foreach ($data as $key => $value) {
if (is_array($value)) {
// 检查是否为数字索引(列表)
if (array_is_list($value)) {
foreach ($value as $item) {
if (is_array($item)) {
$subNode = $xml->addChild($key);
arrayToXml($item, $subNode);
} else {
$xml->addChild($key, (string) $item);
}
}
} else {
$subNode = $xml->addChild($key);
arrayToXml($value, $subNode);
}
} else {
$xml->addChild($key, htmlspecialchars((string) $value));
}
}
}
// 使用示例
$data = [
'database' => [
'host' => 'localhost',
'port' => 3306,
'name' => 'myapp',
'user' => 'root',
'password' => 'secret',
],
'cache' => [
'driver' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
],
];
$xml = new SimpleXMLElement('<config></config>');
arrayToXml($data, $xml);
$dom = dom_import_simplexml($xml)->ownerDocument;
$dom->formatOutput = true;
echo $dom->saveXML($dom->documentElement);实战示例
RSS Feed 解析
php
<?php
declare(strict_types=1);
class RssParser
{
public function parse(string $url): array
{
$xml = @simplexml_load_file($url);
if ($xml === false) {
throw new RuntimeException("RSS 加载失败");
}
$channel = $xml->channel;
$feed = [
'title' => (string) $channel->title,
'description' => (string) $channel->description,
'link' => (string) $channel->link,
'items' => [],
];
foreach ($channel->item as $item) {
$feed['items'][] = [
'title' => (string) $item->title,
'link' => (string) $item->link,
'description' => (string) $item->description,
'pubDate' => (string) $item->pubDate,
'guid' => (string) $item->guid,
];
}
return $feed;
}
public function toHtml(array $feed): string
{
$html = "<h2>{$feed['title']}</h2>";
$html .= "<p>{$feed['description']}</p>";
$html .= '<ul>';
foreach ($feed['items'] as $item) {
$html .= "<li><a href=\"{$item['link']}\">{$item['title']}</a>";
$html .= " ({$item['pubDate']})</li>";
}
$html .= '</ul>';
return $html;
}
}
// 使用示例
// $parser = new RssParser();
// $feed = $parser->parse('https://example.com/feed.xml');
// echo $parser->toHtml($feed);注意事项
常见陷阱
php
<?php
declare(strict_types=1);
$book = simplexml_load_string('<book><price>29.99</price></book>');
// 错误:直接比较 SimpleXMLElement
// if ($book->price == 29.99) { } // 可能不准确
// 正确:先转换为标量
if ((float) $book->price == 29.99) { }
// 错误:使用 isset 检查空元素
// if (isset($book->price)) { } // 即使为空也返回 true
// 正确:检查子元素是否存在
$children = $book->children();
if (isset($children->price) && (string) $children->price !== '') { }
// 错误:使用 count 判断子元素数量
// count($book->chapter) // 可能不可靠
// 正确:使用 xpath 或 children()->count()SimpleXML 与 DOM 互转
php
<?php
declare(strict_types=1);
// SimpleXML -> DOM
$library = simplexml_load_string($xmlString);
$dom = dom_import_simplexml($library);
$domDoc = $dom->ownerDocument;
// DOM -> SimpleXML
$simpleXml = simplexml_import_dom($domDoc);最佳实践
- 始终进行类型转换:使用
(string)、(int)、(float)转换 SimpleXMLElement - 使用 xpath 进行复杂查询:比手动遍历更简洁
- 检查返回值:
simplexml_load_string()和simplexml_load_file()可能返回 false - 使用
dom_import_simplexml删除节点:SimpleXML 原生不支持删除操作 - 大文件用 XMLReader:SimpleXML 将整个文档加载到内存
- 注册命名空间:处理带命名空间的 XML 时先注册
下一节