XML with SimpleXML

PHP · Lesson 29 of 50 8 min read Updated

XML (eXtensible Markup Language) is a text format for structured data. It looks like HTML, but you invent your own tags. Most new web APIs use JSON, yet XML is still everywhere: RSS news feeds, the sitemap.xml that search engines read, SOAP web services used by banks and government systems, and office files (a .docx file is a zip of XML files). A PHP developer meets it sooner or later.

In this lesson you will read XML with SimpleXML, PHP's easiest XML tool, search it with XPath, create XML, handle broken files and namespaces, and use DOMDocument when you need more control. At the end you will see why JSON replaced XML for most APIs.

What XML looks like

Here is a small book catalogue. Save it as catalogue.xml; the examples below read it.

XML
<?xml version="1.0" encoding="UTF-8"?>
<catalogue shop="Jaipur Book House">
    <book id="b1" lang="en">
        <title>Wings of Fire</title>
        <author>A. P. J. Abdul Kalam</author>
        <price currency="INR">350</price>
    </book>
    <book id="b2" lang="hi">
        <title>Godaan</title>
        <author>Premchand</author>
        <price currency="INR">199</price>
    </book>
    <book id="b3" lang="en">
        <title>Malgudi Days</title>
        <author>R. K. Narayan</author>
        <price currency="INR">275</price>
    </book>
</catalogue>

The first line is the XML declaration. There must be exactly one root element (<catalogue>) that contains everything else. Elements can have attributes (id="b1") and child elements. XML is strict: every tag must close, tags are case-sensitive, attribute values need quotes, and a & in text must be written &amp;. A file that follows these rules is called well-formed.

Reading XML with SimpleXML

simplexml_load_file() reads a file and simplexml_load_string() reads a string. Both return a SimpleXMLElement object. Child elements become properties, repeated elements can be looped over or picked by index, and attributes are read like array keys:

PHP
<?php
declare(strict_types=1);

$catalogue = simplexml_load_file(__DIR__ . '/catalogue.xml');

echo $catalogue['shop'], "\n";                 // an attribute
echo $catalogue->book[1]->title, "\n";         // second <book>, its <title>
echo count($catalogue->book), " books\n\n";

$total = 0;
foreach ($catalogue->book as $book) {
    $price = (int) $book->price;               // cast before doing maths
    $total += $price;
    echo "{$book['id']}: {$book->title} by {$book->author}, ₹$price\n";
}
echo "Total: ₹$total\n";

var_dump($catalogue->book[1]->title === 'Godaan');
var_dump((string) $catalogue->book[1]->title === 'Godaan');
Output
Jaipur Book House
Godaan
3 books

b1: Wings of Fire by A. P. J. Abdul Kalam, ₹350
b2: Godaan by Premchand, ₹199
b3: Malgudi Days by R. K. Narayan, ₹275
Total: ₹824
bool(false)
bool(true)

The last two lines show the biggest SimpleXML trap. $book->title is not a string: it is another SimpleXMLElement object. echo converts it to text automatically, but === compares an object with a string and says false. Cast values with (string), (int) or (float) before comparing them, storing them in an array or doing maths.

Searching with XPath

XPath is a small query language for XML, a bit like CSS selectors. $xml->xpath() runs a query and returns an array of matching elements (an empty array if nothing matches). A few patterns cover most needs:

XPathFinds
//bookEvery <book> anywhere in the document
/catalogue/book<book> elements directly inside the root
//book[@lang="en"]Books whose lang attribute is en
//book[price < 300]Books with a <price> child below 300
//book/titleThe <title> of every book
PHP
<?php
declare(strict_types=1);

$catalogue = simplexml_load_file(__DIR__ . '/catalogue.xml');

// All English books
foreach ($catalogue->xpath('//book[@lang="en"]/title') as $title) {
    echo $title, "\n";
}

// Books under ₹300
$cheap = $catalogue->xpath('//book[price < 300]');
echo count($cheap), " books under ₹300\n";

// One book by its id attribute
$found = $catalogue->xpath('//book[@id="b2"]');
echo $found[0]->title, ' by ', $found[0]->author, "\n";
Output
Wings of Fire
Malgudi Days
2 books under ₹300
Godaan by Premchand

Comparisons with < and > in XPath work on numbers only. Dates written as text, such as 2026-10-25, cannot be compared that way; loop over the elements and compare them in PHP instead.

When the XML is broken

Files from other systems are not always well-formed. By default, a parsing error prints PHP warnings and the load function returns false. Turn on internal error handling so you can report problems your own way:

PHP
<?php
declare(strict_types=1);

$broken = '<order><item>Chai<item></order>';

libxml_use_internal_errors(true);          // collect errors instead of printing warnings
$xml = simplexml_load_string($broken);

if ($xml === false) {
    foreach (libxml_get_errors() as $error) {
        echo 'Line ', $error->line, ': ', trim($error->message), "\n";
    }
    libxml_clear_errors();
}
Output
Line 1: Opening and ending tag mismatch: item line 1 and order
Line 1: Premature end of data in tag item line 1

The messages come from libxml, the C library behind all of PHP's XML tools, so the exact wording can differ slightly between servers. Always check for false before you use the result.

Writing XML with SimpleXML

To create XML, start with a SimpleXMLElement holding just the root, then add children and attributes. asXML() returns the finished document as a string, or writes it to a file if you pass a file name:

PHP
<?php
declare(strict_types=1);

$order = new SimpleXMLElement('<order/>');
$order->addAttribute('id', 'ORD-1042');

$customer = $order->addChild('customer');
$customer->addChild('name', 'Farhan Qureshi');
$customer->addChild('city', 'Lucknow');

$item = $order->addChild('item');
$item->addAttribute('qty', '2');
$item->name = 'Rohan & Sons Pickle';      // assigning escapes & safely
$item->price = 240;

echo $order->asXML();
Output
<?xml version="1.0"?>
<order id="ORD-1042"><customer><name>Farhan Qureshi</name><city>Lucknow</city></customer><item qty="2"><name>Rohan &amp; Sons Pickle</name><price>240</price></item></order>

Watch the & in the shop name. addChild('name', 'Rohan & Sons') does not escape it: PHP shows the warning "unterminated entity reference" and leaves the element empty. Either assign the value through a property, as above, or escape it first with htmlspecialchars($value, ENT_XML1). Also notice that SimpleXML writes everything on one line. That is valid XML, but hard for humans to read.

DOMDocument: more control

The DOM extension gives you the full Document Object Model, the same idea as the DOM in JavaScript. It is wordier than SimpleXML but can do everything: pretty printing, namespaces, inserting nodes anywhere, and HTML. Here it builds a search-engine sitemap. Sitemaps use a namespace, a URL that says which vocabulary the tags belong to:

PHP
<?php
declare(strict_types=1);

$pages = [
    ['loc' => 'https://example.in/', 'lastmod' => '2026-10-28'],
    ['loc' => 'https://example.in/menu?veg=1&spicy=0', 'lastmod' => '2026-10-20'],
];

$doc = new DOMDocument('1.0', 'UTF-8');
$doc->formatOutput = true;                       // indent the result

$urlset = $doc->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'urlset');
$doc->appendChild($urlset);

foreach ($pages as $page) {
    $url = $doc->createElement('url');
    $loc = $doc->createElement('loc');
    $loc->appendChild($doc->createTextNode($page['loc']));   // text nodes are escaped for you
    $url->appendChild($loc);
    $url->appendChild($doc->createElement('lastmod', $page['lastmod']));
    $urlset->appendChild($url);
}

echo $doc->saveXML();
Output
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.in/</loc>
    <lastmod>2026-10-28</lastmod>
  </url>
  <url>
    <loc>https://example.in/menu?veg=1&amp;spicy=0</loc>
    <lastmod>2026-10-20</lastmod>
  </url>
</urlset>

createTextNode() escapes the & in the URL automatically. Namespaces affect XPath too: when you load this sitemap with SimpleXML, xpath('//loc') finds nothing, because loc lives in the sitemap namespace. Register a prefix first, then use it in the query: $xml->registerXPathNamespace('s', 'http://www.sitemaps.org/schemas/sitemap/0.9'); followed by $xml->xpath('//s:loc').

PHP 8.4 also added a modern, standards-compliant DOM API in the Dom namespace. Dom\XMLDocument::createFromFile() loads a file, and you can search with CSS selectors such as $doc->querySelectorAll('book[lang="en"] > title'), just like in the browser. Dom\HTMLDocument parses real-world HTML5 pages correctly, which the older DOMDocument never fully could.

Why JSON replaced XML for most APIs

During the 2000s and 2010s, most new web APIs moved from XML to JSON. The reasons are easy to see side by side:

XMLJSON
SizeOpening and closing tags repeat every nameShorter: names appear once
In PHPObjects you must cast, XPath, namespacesjson_decode() gives plain arrays
In JavaScriptNeeds a parser and DOM codeBuilt in: JSON.parse(), fetch().json()
Data typesEverything is textNumbers, booleans, null, arrays
Strong pointsAttributes, mixed text, namespaces, schemasSimple data exchange

JSON maps directly onto arrays and objects in PHP and JavaScript, so it is simpler for the typical "send me some records" API. XML remains the better fit for documents (text with markup inside), for formats with strict schemas, and for older enterprise systems. For JSON in PHP, see JSON & APIs.

Common mistakes

  • Comparing SimpleXML values without casting. $book->title === 'Godaan' is false; cast with (string) first.
  • Not checking for false. A broken file makes simplexml_load_file() return false, and the next line fails.
  • Putting & in addChild() text. Assign through a property or escape with htmlspecialchars($v, ENT_XML1).
  • Forgetting namespaces in XPath. Register a prefix with registerXPathNamespace() for feeds and sitemaps.
  • Building XML by joining strings. One unescaped < or & breaks the whole file; use SimpleXML or DOM.

Key points

  • XML must be well-formed: one root, closed tags, quoted attributes, &amp; for &.
  • SimpleXML turns elements into properties and attributes into array keys; cast values before using them.
  • xpath() searches with paths like //book[@lang="en"] and returns an array of elements.
  • Use libxml_use_internal_errors(true) and libxml_get_errors() to handle broken input.
  • SimpleXMLElement::addChild() and asXML() write XML; DOMDocument adds pretty printing and full control.
  • JSON is simpler for data APIs; XML lives on in feeds, sitemaps, SOAP and documents.

Exercise

Load this XML with simplexml_load_string(): a <class name="10-B"> root with three <student> elements: Aarav (roll 1, marks 88), Meera (roll 2, marks 64) and Kabir (roll 3, marks 91), with roll and marks as attributes and the name as text. Add a fourth student, Isha (roll 4, marks 79). Then use XPath to print every student with 80 marks or more, and print the class average with one decimal place.

Show solution
PHP
<?php
declare(strict_types=1);

$xml = simplexml_load_string(<<<XML
<class name="10-B">
    <student roll="1" marks="88">Aarav</student>
    <student roll="2" marks="64">Meera</student>
    <student roll="3" marks="91">Kabir</student>
</class>
XML);

$new = $xml->addChild('student', 'Isha');
$new->addAttribute('roll', '4');
$new->addAttribute('marks', '79');

foreach ($xml->xpath('//student[@marks >= 80]') as $s) {
    echo "Topper: $s ({$s['marks']})\n";
}

$sum = 0;
foreach ($xml->student as $s) {
    $sum += (int) $s['marks'];
}
printf("Average of %d students: %.1f\n", count($xml->student), $sum / count($xml->student));
Output
Topper: Aarav (88)
Topper: Kabir (91)
Average of 4 students: 80.5

The XPath @marks >= 80 compares attribute values as numbers. In the loop, (int) turns each attribute into a number before adding it up.

Quick Quiz

1. What does $catalogue->book[0]['id'] read?

2. Why is $book->title === 'Godaan' false even when the title is Godaan?

3. Which XPath finds every book whose lang attribute is hi?

4. What happens with addChild('name', 'Rohan & Sons')?

Learn PHP with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call