PHP XML Guide: SimpleXML, XMLReader, and XMLWriter

Published Updated

XML has fallen out of fashion for new web APIs, but it has not disappeared from real PHP work. RSS feeds, old content exports, banking files, shipping integrations, government services, and enterprise systems still hand you XML and expect you to parse it without drama.

The answer is narrow. Use XML when the contract is already XML, and use JSON when you control the new contract. The job in either case is the same, which is to read a feed, pull values out of a document, and turn someone else's structured data into application data.

A parser makes the document easier to read, and it does not make the data trustworthy.

Guide Path

Read this after working with text files in PHP if the file is structured XML instead of plain text. When you control the new integration contract, jump ahead to the custom API capstone instead of inventing new XML.

How Do You Read XML with PHP SimpleXML?

Read a small XML document with simplexml_load_string(), check for failure, then access its elements as properties. PHP must have the SimpleXML extension enabled.

<?php

$xml = <<<XML
<catalog>
    <book id="cw-101">
        <title>Practical PHP</title>
        <author>CodeWalkers</author>
    </book>
</catalog>
XML;

$catalog = simplexml_load_string($xml);

if ($catalog === false) {
    throw new RuntimeException("Could not parse XML.");
}

foreach ($catalog->book as $book) {
    printf(
        "%s by %s\n",
        (string) $book->title,
        (string) $book->author,
    );
}

Those explicit casts matter at the application boundary. A SimpleXMLElement is not the same thing as a string, even when it prints like one. Cast values at the boundary where the data enters your own model.

How Do You Handle PHP XML Parse Errors?

Handle XML parse failures by checking for false and collecting libxml errors. Common causes include a truncated upload, a bad character, a provider response wrapped in an HTML error page, or a feed that changed its namespace. Treat those failures as input errors.

<?php

$previousErrorMode = libxml_use_internal_errors(true);
libxml_clear_errors();

$document = simplexml_load_string($xml);

if ($document === false) {
    $messages = [];

    foreach (libxml_get_errors() as $error) {
        $messages[] = trim($error->message);
    }

    libxml_clear_errors();
    libxml_use_internal_errors($previousErrorMode);

    throw new RuntimeException(
        "Invalid XML: " . implode("; ", $messages),
    );
}

libxml_clear_errors();
libxml_use_internal_errors($previousErrorMode);

Check with === false, not a loose truthiness check. An empty but valid XML document can look falsey in PHP. That is the kind of detail that makes old XML code feel haunted when the real problem is just a loose comparison.

How Do You Read Large XML Files in PHP?

Read large XML files in PHP with XMLReader, which moves through the document one node at a time. SimpleXML loads the whole document into memory, so reserve it for smaller files.

<?php

$reader = XMLReader::open(
    __DIR__ . "/../storage/books.xml",
    null,
    LIBXML_NONET,
);

if ($reader === false) {
    throw new RuntimeException("Could not open XML file.");
}

while ($reader->read()) {
    if ($reader->nodeType !== XMLReader::ELEMENT || $reader->name !== "book") {
        continue;
    }

    $book = simplexml_load_string($reader->readOuterXml());

    if ($book === false) {
        continue;
    }

    importBook([
        "id" => (string) $book["id"],
        "title" => (string) $book->title,
        "author" => (string) $book->author,
    ]);
}

$reader->close();

The example assumes your application defines importBook(). XMLReader avoids loading the whole document, but readOuterXml() still loads each matching record into a string. A single huge record can consume substantial memory; set file and record limits and check parse errors rather than treating every early stop as success. If the next step writes rows into SQL, batch the writes and use prepared statements. XML carries the data, but you still need to choose suitable database tables, column types and relationships. The schema design guide covers that side of the work.

How Do You Read XML Namespaces with SimpleXML?

Read namespaced elements with SimpleXML’s children() method and the namespace URI. If your code ignores them, it may work against one sample and fail against the real feed.

<?php

$rssXml = '<rss xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><item><title>PHP Guide</title><dc:creator>Mark</dc:creator></item></channel></rss>';
$feed = simplexml_load_string($rssXml);

if ($feed === false) {
    throw new RuntimeException("Invalid RSS XML.");
}

$namespaces = $feed->getNamespaces(true);

foreach ($feed->channel->item as $item) {
    $dc = $item->children($namespaces["dc"] ?? "");

    echo htmlspecialchars((string) $item->title, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
    echo htmlspecialchars((string) $dc->creator, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
}

If you remember the old "Using Modules in Your RSS Feed" tutorial, this is its modern shape, because RSS modules are a namespace problem before they are a PHP trick. Do not strip namespaces to make the code shorter. That usually means throwing away the part of the contract that made the feed useful.

How Do You Generate a Sitemap with PHP?

Generate a PHP sitemap with XMLWriter, using your application’s canonical URLs and each page’s real modification date.

Do this at publish time, build time, or on a cacheable admin job. Do not rebuild the sitemap by scanning live tables on every public request unless the site is tiny and you have measured it.

XMLWriter keeps the output well formed without hand-concatenating tags:

<?php

$routes = [
    [
        "loc" => "https://example.com/programming/php/",
        "lastmod" => new DateTimeImmutable("2026-06-06"),
    ],
    [
        "loc" => "https://example.com/programming/php/mysql/pdo/",
        "lastmod" => new DateTimeImmutable("2026-06-06"),
    ],
];

$writer = new XMLWriter();
$writer->openMemory();
$writer->startDocument("1.0", "UTF-8");
$writer->startElement("urlset");
$writer->writeAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");

foreach ($routes as $route) {
    $writer->startElement("url");
    $writer->writeElement("loc", $route["loc"]);
    $writer->writeElement("lastmod", $route["lastmod"]->format("Y-m-d"));
    $writer->endElement();
}

$writer->endElement();
$writer->endDocument();

$sitemapXml = $writer->outputMemory();

The useful rules are small: include absolute canonical URLs, keep them on the same host, escape values through the XML writer, and set lastmod to the page's real content modification date rather than the time the sitemap file happened to regenerate. For larger sites, split files before the protocol limits and use a sitemap index.

If your framework or static-site builder already emits a sitemap from routes and frontmatter, use that. Custom PHP generation is for inherited systems, internal tools, and migrations where PHP still owns the URL inventory.

How Do You Parse Untrusted XML Safely?

Parse untrusted XML with external entities and external DTD loading disabled, and limit document size and nesting. These controls reduce the risk of file disclosure and excessive resource use.

In current PHP, the libxml constants matter. LIBXML_NONET disables network access when loading documents. PHP 8.4 also exposes LIBXML_NO_XXE when built with a new enough libxml version. The older libxml_disable_entity_loader() path is listed as deprecated in the PHP manual, so do not copy old snippets that begin there.

For ordinary untrusted input, keep the load step explicit:

<?php

$flags = LIBXML_NONET;

if (defined("LIBXML_NO_XXE")) {
    $flags |= LIBXML_NO_XXE;
}

$document = simplexml_load_string($xml, SimpleXMLElement::class, $flags);

if ($document === false) {
    throw new RuntimeException("Invalid XML.");
}

Do not enable entity substitution or DTD loading for untrusted XML. LIBXML_NONET blocks network access, not every local-file operation. Limit document size and nesting, and keep PHP and libxml patched.

Should You Use XML or JSON in a PHP API?

Use XML in a PHP API when an existing integration requires it. If you control both sides of a new API, JSON over HTTP is usually simpler to debug, document, cache, and test. PHP's JSON functions are first-class, and the wider tooling around OpenAPI assumes JSON as the common case.

Choose XML when these constraints are real:

  • A provider already requires it.
  • You are reading RSS, Atom, sitemap, or legacy export files.
  • The schema, namespaces, or existing contract are genuinely XML-shaped.
  • A SOAP or XML-RPC system is the thing you must integrate with.

Choose JSON when you control the new boundary:

  • You own the new API contract.
  • Browser or mobile clients will consume it.
  • The data model does not need XML namespaces or mixed content.
  • You want a smaller request surface for a new service.

Read build your own API with PHP for the new-contract path, inherited XML-RPC systems that need a migration plan, or SOAP providers where WSDL is the contract you inherited.

Frequently Asked Questions

What is the difference between SimpleXML and DOMDocument?

SimpleXML gives a small read-oriented object you can walk with property syntax, which suits well-formed documents you mostly read. DOMDocument exposes the full node tree, so it is the one to use for editing, moving nodes, or building a document.

Can you convert XML into a PHP array?

Convert XML into a PHP array by reading the required elements and attributes into explicitly named keys. A SimpleXML-to-JSON round trip can lose attributes, namespaces or mixed content, so it is not a reliable general conversion.

How do you generate XML with PHP?

Generate XML in PHP with XMLWriter for streamed output such as large sitemaps, or DOMDocument when you need to build and manipulate a tree before saving. Both escape values correctly, which string concatenation does not.

How do you read attributes with SimpleXML?

Read SimpleXML attributes with array syntax on the element, then cast to the type you need, because the result is a SimpleXMLElement rather than a string. Attributes in a namespace require passing that namespace when you request them.

Should a sitemap be generated on every request?

A sitemap usually does not need to be generated on every request. Build it when content changes or on a schedule and serve the stored file. Beyond the repeated work, regenerating per request tends to stamp every URL with the current time rather than its real last-changed date, and a lastmod that always says now is one crawlers learn to ignore.

Sources

  1. [1]
    PHP SimpleXML
    (php.net)
  2. [2]
  3. [3]
    PHP XMLReader
    (php.net)
  4. [4]
  5. [5]
    PHP XMLWriter
    (php.net)
  6. [6]
  7. [7]
  8. [8]
  9. [9]
    Sitemaps XML format
    (sitemaps.org)
  10. [10]
    Build and Submit a Sitemap
    (developers.google.com)