XML DOM

Loading XML as a node tree, traversing and modifying it, and generating XML.

Intermediate
Key PointsDOM loads the whole document into a tree — good for modification and complex queries.

Reading XML

PHP
<?php
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<books>
  <book id="1"><title>PHP 入门</title><price>59</price></book>
  <book id="2"><title>PHP 进阶</title><price>79</price></book>
</books>';

$doc = new DOMDocument();
$doc->loadXML($xml);

$books = $doc->getElementsByTagName("book");
foreach ($books as $b) {
    $title = $b->getElementsByTagName("title")->item(0)->nodeValue;
    $price = $b->getElementsByTagName("price")->item(0)->nodeValue;
    echo $b->getAttribute("id") . " " . $title . " " . $price . "\n";
}

echo "共 " . $books->length . " 本\n";
echo "根元素: " . $doc->documentElement->nodeName . "\n";
Output
1 PHP 入门 59
2 PHP 进阶 79
共 2 本
根元素: books

XPath

getElementsByTagName filters by name only. For attributes, positions and paths, use XPath.

PHP
<?php
$doc = new DOMDocument();
$doc->loadXML($xml);

$xp = new DOMXPath($doc);

echo $xp->query("/books/book[@id='2']/title")->item(0)->nodeValue, "\n";

echo $xp->query("//book[price > 60]/title")->item(0)->nodeValue, "\n";

echo $xp->evaluate("count(//book)"), "\n";

echo $xp->evaluate("sum(//price)"), "\n";
Output
PHP 进阶
PHP 进阶
2
138

query() returns a node set; evaluate() returns a scalar. Mixing them yields empty results rather than errors.

Building XML

PHP
<?php
$doc = new DOMDocument("1.0", "UTF-8");
$doc->formatOutput = true;

$root = $doc->createElement("books");
$doc->appendChild($root);

foreach ([["1", "PHP 入门", 59], ["2", "PHP 进阶", 79]] as [$id, $title, $price]) {
    $book = $doc->createElement("book");
    $book->setAttribute("id", $id);

    $t = $doc->createElement("title");
    $t->appendChild($doc->createTextNode($title));
    $book->appendChild($t);

    $pr = $doc->createElement("price", (string)$price);
    $book->appendChild($pr);

    $root->appendChild($book);
}

echo $doc->saveXML();
Output
<?xml version="1.0" encoding="UTF-8"?>
<books>
  <book id="1">
    <title>PHP 入门</title>
    <price>59</price>
  </book>
  <book id="2">
    <title>PHP 进阶</title>
    <price>79</price>
  </book>
</books>
createElement's second argument becomes text, but any < or & needs createTextNode. **Always use createTextNode for user data.**

Modifying XML

PHP
<?php
$doc = new DOMDocument();
$doc->loadXML($xml);
$doc->formatOutput = true;

$xp = new DOMXPath($doc);

// 改文本
$xp->query("//book[@id='1']/price")->item(0)->nodeValue = "49";

// 加属性
$xp->query("//book[@id='2']")->item(0)->setAttribute("stock", "10");

// 删节点
$del = $xp->query("//book[@id='2']/price")->item(0);
$del->parentNode->removeChild($del);

echo $doc->saveXML();
Output
<?xml version="1.0" encoding="UTF-8"?>
<books>
  <book id="1">
    <title>PHP 入门</title>
    <price>49</price>
  </book>
  <book id="2" stock="10">
    <title>PHP 进阶</title>
  </book>
</books>

Removal goes through the parent: $node->parentNode->removeChild($node).

From files and strings

PHP
<?php
$path = __DIR__ . "/data.xml";
file_put_contents($path, '<r><v>1</v></r>');

// 从文件
$doc = new DOMDocument();
$ok = $doc->load($path);
echo $ok ? "load 成功" : "load 失败", "\n";
echo $doc->getElementsByTagName("v")->item(0)->nodeValue, "\n";

// 保存到文件
$doc->save($path);
echo filesize($path) > 0 ? "已保存" : "保存失败", "\n";

unlink($path);
Output
load 成功
1
已保存
load() spews warnings on failure. Call libxml_use_internal_errors(true) and read libxml_get_errors() yourself.
PHP
<?php
libxml_use_internal_errors(true);

$doc = new DOMDocument();
$ok = $doc->loadXML("<r><unclosed></r>");

if (!$ok) {
    foreach (libxml_get_errors() as $e) {
        printf("第 %d 行: %s", $e->line, trim($e->message) . "\n");
    }
    libxml_clear_errors();
}

echo $ok ? "解析成功" : "解析失败", "\n";
Output
第 1 行: Opening and ending tag mismatch: unclosed line 1 and r
解析失败

Security: disable external entities

By default external entities may be resolved, enabling XXE — reading /etc/passwd via <!ENTITY xxe SYSTEM "file:///etc/passwd">. PHP 8 disables it by default; older versions need this explicit.

PHP
<?php
$doc = new DOMDocument();

// 解析用户上传的 XML:禁止网络加载
$ok = $doc->loadXML('<r><v>1</v></r>', LIBXML_NONET);

echo $ok ? "解析成功" : "解析失败", "\n";
echo $doc->getElementsByTagName("v")->item(0)->nodeValue, "\n";
Output
解析成功
1
LIBXML_NONET blocks network loading. Before PHP 8 you also needed libxml_disable_entity_loader(true) to block file://; it is deprecated since 8.0 because the default is now safe. Mandatory for user-uploaded XML.

Friends & Partners