Skip to content

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);

最佳实践

  1. 始终进行类型转换:使用 (string)(int)(float) 转换 SimpleXMLElement
  2. 使用 xpath 进行复杂查询:比手动遍历更简洁
  3. 检查返回值simplexml_load_string()simplexml_load_file() 可能返回 false
  4. 使用 dom_import_simplexml 删除节点:SimpleXML 原生不支持删除操作
  5. 大文件用 XMLReader:SimpleXML 将整个文档加载到内存
  6. 注册命名空间:处理带命名空间的 XML 时先注册

下一节

继续学习:XMLReader / XMLWriter

参考链接