PHP XMLWriter
What Is XMLWriter? In simple terms: XMLWriter creates XML documents step by step: "start an element", "add an attribute", "write some text", "close the element". It handles escaping and indentation fo
What Is XMLWriter?
In simple terms: XMLWriter creates XML documents step by step: "start an element", "add an attribute", "write some text", "close the element". It handles escaping and indentation for you and can write directly to a file or the browser while generating — so even a sitemap with a million URLs can be produced without building it in memory first.
Technically: The XMLWriter class is a forward-only, streaming XML generator built on libxml. Output goes to memory (openMemory()) or to a URI/stream (openUri()), and can be flushed in portions with flush(). It is the writing counterpart of XMLReader: fast and memory-efficient, but it cannot go back and modify what was already written.
Choosing an XML Generation Tool
| Tool | Best For |
|---|---|
| XMLWriter | Large documents, streaming output, sitemaps, exports, feeds |
| DOM | Building or editing documents that need changes before saving |
| SimpleXML | Small, simple documents |
| String concatenation | Never — escaping mistakes produce invalid XML or injection problems |
Writing a Document
<?php
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString(' ');
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('order');
$writer->writeAttribute('id', '501');
$writer->startElement('item');
$writer->writeAttribute('sku', 'PEN-01');
$writer->writeElement('name', 'Gel Pen');
$writer->writeElement('quantity', '10');
$writer->endElement(); // </item>
$writer->writeElement('note', 'Ring the bell & wait');
$writer->endElement(); // </order>
$writer->endDocument();
echo $writer->outputMemory();
Output
<?xml version="1.0" encoding="UTF-8"?>
<order id="501">
<item sku="PEN-01">
<name>Gel Pen</name>
<quantity>10</quantity>
</item>
<note>Ring the bell & wait</note>
</order>
How It Works
| Method | Effect |
|---|---|
openMemory() | Collect output in memory (retrieved with outputMemory()) |
setIndent(true), setIndentString(' ') | Pretty-print with two spaces per level |
startDocument('1.0', 'UTF-8') | Write the XML declaration |
startElement('order') … endElement() | Open and close an element; elements can be nested |
writeAttribute('id', '501') | Add an attribute to the element just started (before writing content) |
writeElement('name', 'Gel Pen') | Shortcut: start, write text, and end an element in one call |
text($content) | Write text inside the current element |
endDocument() | Close all open elements and finish the document |
Text and attribute values are escaped automatically: & became &. Values must be strings; convert numbers with (string) or string interpolation.
Practical Example: Generating a Sitemap
Search engines read sitemaps to discover the pages of a website. A sitemap is an XML document in the http://www.sitemaps.org/schemas/sitemap/0.9 namespace:
<?php
function writeSitemap(XMLWriter $writer, iterable $pages): void
{
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('urlset');
$writer->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
$count = 0;
foreach ($pages as $page) {
$writer->startElement('url');
$writer->writeElement('loc', $page['url']);
$writer->writeElement('lastmod', $page['updated']->format('Y-m-d'));
$writer->endElement();
if (++$count % 1000 === 0) {
$writer->flush(); // write buffered output to the destination
}
}
$writer->endElement();
$writer->endDocument();
$writer->flush();
}
$pages = [
['url' => 'https://gocourse.in/products/gel-pen', 'updated' => new DateTimeImmutable('2026-10-15')],
['url' => 'https://gocourse.in/products/desk-lamp', 'updated' => new DateTimeImmutable('2026-10-12')],
];
$writer = new XMLWriter();
$writer->openUri('php://output');
$writer->setIndent(true);
$writer->setIndentString(' ');
writeSitemap($writer, $pages);
Output
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://gocourse.in/products/gel-pen</loc>
<lastmod>2026-10-15</lastmod>
</url>
<url>
<loc>https://gocourse.in/products/desk-lamp</loc>
<lastmod>2026-10-12</lastmod>
</url>
</urlset>
Step-by-Step Explanation
openUri('php://output')sends the XML directly to the output (the browser, or the terminal in the command line). For a file, useopenUri('/path/to/sitemap.xml').- The default namespace is declared with a normal
xmlnsattribute on the root element. $pagesisiterable, so it can be a generator that fetches rows from the database one at a time (see PHP Generators). Combined withflush()every 1,000 URLs, memory use stays constant for any number of pages.- A final
flush()writes whatever remains in the buffer.
When serving the sitemap from a PHP script, send header('Content-Type: application/xml; charset=utf-8'); before writing. (A single sitemap file may contain at most 50,000 URLs; larger sites use a sitemap index.)
CDATA, Comments, and Namespaces
<?php
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString(' ');
$writer->startDocument('1.0', 'UTF-8');
$writer->writeComment(' Generated by GoCourse ');
$writer->startElementNs('g', 'feed', 'http://example.com/ns/shop');
$writer->startElement('description');
$writer->writeCdata('<p>Bold & bright <b>LED</b> lamp</p>');
$writer->endElement();
$writer->endElement();
$writer->endDocument();
echo $writer->outputMemory();
Output
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by GoCourse -->
<g:feed xmlns:g="http://example.com/ns/shop">
<description><![CDATA[<p>Bold & bright <b>LED</b> lamp</p>]]></description>
</g:feed>
writeCdata()writes a CDATA section: content that is taken literally, without escaping — useful for embedding HTML snippets that the receiving system expects in that form. CDATA cannot contain the sequence]]>.writeComment()adds an XML comment.startElementNs($prefix, $name, $uri)creates a namespaced element and declares the namespace.
PHP 8.4: Static Constructors
PHP 8.4 added clearer factory methods:
$writer = XMLWriter::toMemory();
$writer = XMLWriter::toUri('/var/www/public/sitemap.xml');
$writer = XMLWriter::toStream($resource);
They replace new XMLWriter() followed by openMemory() / openUri().
Common Mistakes
Writing Attributes After Content
Effect: Attributes are silently not written (or the call fails) once text or child elements have been added.
Fix: Call writeAttribute() immediately after startElement().
Unbalanced startElement() / endElement()
Effect: Elements are closed at the wrong level.
Fix: Keep start and end calls paired, or wrap sections in small functions; endDocument() closes any remaining elements.
Passing Non-String Values
Effect: TypeError in strict mode for integers or floats.
Fix: Convert values to strings explicitly.
Building XML With String Concatenation
Effect: Unescaped & or < produce broken documents.
Fix: Use XMLWriter, which escapes automatically.
Best Practices
- Use XMLWriter for generating large XML documents and streaming output.
- Enable indentation only when humans read the output; omit it for smaller files.
- Feed data from generators and
flush()periodically for constant memory use. - Send the correct
Content-Typeheader when serving XML over HTTP. - Use DOM instead when the document must be modified after creation.
Official reference: https://www.php.net/manual/en/book.xmlwriter.php