Processing XML with PHP
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.
SimpleXML
SimpleXML is the right starting point when the XML is small and you know the shape. The PHP manual describes it as a simple toolset that converts XML into an object you can walk with properties and iterators, which is exactly its lane.
<?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.
Handle Parse Errors Deliberately
Most production XML failures are boring: 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, not as mysterious PHP behavior.
<?php
libxml_use_internal_errors(true);
$document = simplexml_load_string($xml);
if ($document === false) {
$messages = [];
foreach (libxml_get_errors() as $error) {
$messages[] = trim($error->message);
}
libxml_clear_errors();
throw new RuntimeException(
"Invalid XML: " . implode("; ", $messages),
);
}
libxml_clear_errors(); 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.
Use XMLReader for Large Feeds
SimpleXML loads the whole XML document into memory. That is fine for a short config file and painful for a large export. XMLReader is a pull parser: it moves forward through the document stream and stops at each node.
<?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(); That pattern keeps memory bounded because you process one matching node at a time. If the next step writes rows into SQL, batch the writes and use prepared statements. The XML file is only the transport; your database model still has to be honest. The schema design guide covers that side of the work.
Namespaces
Feed modules and provider XML often use namespaces. If your code ignores them, it may work against one sample and fail against the real feed.
<?php
$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 (string) $item->title;
echo (string) $dc->creator;
} 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.
Generate Sitemaps from Canonical Routes
The old phpBB Google Sitemap XML Generator snippet made sense when a forum needed a one-off PHP script to expose its topics to crawlers. The current version of that lesson is not phpBB-specific. Generate sitemap XML from the same canonical URL list your app, CMS, or static build already trusts.
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.
Untrusted XML
XML has features that are useful in controlled documents and dangerous when the document comes from someone else. External entities are the example that still matters. OWASP's XML External Entity guidance is blunt: disable external entities and external DTD loading for untrusted XML.
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.");
} The important habit is not the exact constant list. It is reading the parser options before letting an uploaded document decide what your server should fetch or expand.
XML vs JSON
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.
This example has been updated from the original CodeWalkers XML tutorials, which parsed feeds back when XML was the default answer for a new integration. The parsing advice survives; the recommendation to reach for XML on a contract you own does not.
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?
You can round-trip SimpleXML through json_encode and json_decode, which is convenient but lossy: attributes, namespaces, and repeated single elements do not survive predictably. Read the values you need directly when the structure matters.
How do you generate XML rather than read it?
Use 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?
Access them 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?
No. 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]
PHP SimpleXML(php.net)
-
[2]
PHP simplexml_load_string(php.net)
-
[3]
PHP XMLReader(php.net)
-
[4]
PHP XMLReader::open(php.net)
-
[5]
PHP XMLWriter(php.net)
-
[6]
PHP Dealing with XML errors(php.net)
-
[7]
PHP libxml predefined constants(php.net)
-
[8]
XML External Entity Prevention Cheat Sheet(cheatsheetseries.owasp.org)
-
[9]
Sitemaps XML format(sitemaps.org)
-
[10]
Build and Submit a Sitemap(developers.google.com)
Read Next
Finish the PHP web-app path with a small JSON API, HTTP methods, status codes, validation, PDO, and an OpenAPI-ready contract.
Secure ordinary PHP surfaces with current password hashing, sessions, CSRF tokens, output escaping, prepared statements, and upload controls.
Modern PHP reference guides for frameworks, PDO, configuration, pagination, conditionals, forms, files, sockets, media streaming, mini chat apps, XML, templates, OOP, APIs, email, SOAP, recursion, SQL-backed apps, and security fundamentals.