PHP XMLReader

Su Subhalakshmi Chandran Updated 21 Oct 2026
5 min read ·Lesson 183 of 185
In this post

What Is XMLReader? In simple terms: XMLReader reads an XML document like a cursor moving through the file, one node at a time. At each step, you look at where the cursor is ("a <product> starts

What Is XMLReader?

In simple terms: XMLReader reads an XML document like a cursor moving through the file, one node at a time. At each step, you look at where the cursor is ("a <product> starts here") and decide what to do: read a value, skip a whole section, or jump to the next record. Only the current position is in memory, so a 5 GB product feed is processed with the same small amount of memory as a 5 KB file.

Technically: XMLReader is a pull parser built on libxml: your code pulls the next node with read() (or jumps with next()), instead of receiving callbacks as with Expat. It is forward-only and read-only. Its most useful feature is expand(), which turns the current element into a DOM node, so each record can be processed comfortably with DOM or SimpleXML while the overall file is streamed.

XMLReader Compared

SimpleXML / DOMExpat (XML Parser)XMLReader
MemoryWhole documentConstantConstant
Programming modelNavigate a treeCallbacks push events to youYou pull nodes in a loop
Ease of useEasiestHardest (manual state tracking)Moderate; easy when combined with expand()
Typical useSmall/medium documentsLegacy streaming codeLarge files, feeds, imports

Opening a Document

$reader = new XMLReader();
$reader->open('/path/to/catalog.xml');   // from a file or URL
$reader->XML($catalogXml);               // or from a string

PHP 8.4 adds static constructors with clearer names: XMLReader::fromUri($path), XMLReader::fromString($xml), and XMLReader::fromStream($resource).


Moving Through Nodes With read()

Each call to read() moves the cursor to the next node and returns false at the end of the document:

<?php
$reader = new XMLReader();
$reader->XML('<order id="501"><item>Pen</item></order>');

while ($reader->read()) {
    $indent = str_repeat('  ', $reader->depth);

    $line = match ($reader->nodeType) {
        XMLReader::ELEMENT => "start <{$reader->name}>" . ($reader->hasAttributes ? ' id=' . $reader->getAttribute('id') : ''),
        XMLReader::TEXT => "text \"{$reader->value}\"",
        XMLReader::END_ELEMENT => "end </{$reader->name}>",
        default => null,
    };

    if ($line !== null) {
        echo $indent . $line . "\n";
    }
}

$reader->close();

Output

start <order> id=501
  start <item>
    text "Pen"
  end </item>
end </order>

Important Properties

Property / MethodMeaning
nodeTypeKind of node: XMLReader::ELEMENT, TEXT, END_ELEMENT, CDATA, SIGNIFICANT_WHITESPACE, …
name / localNameNode name (with / without namespace prefix)
valueText value of text and attribute nodes
depthNesting level (the root element has depth 0)
hasAttributes, getAttribute($name)Attribute access on element nodes
isEmptyElementtrue for self-closing elements like <br/> (no END_ELEMENT follows)
readString()The text content of the current element
readOuterXml() / readInnerXml()The current element's XML as a string

Streaming Records: next() and expand()

Processing a large catalog record by record combines XMLReader's streaming with SimpleXML's convenience:

<?php
$reader = new XMLReader();
$reader->XML($catalogXml);   // for a real file: $reader->open('catalog.xml')

$doc = new DOMDocument();
$processed = 0;
$lowStock = [];

// Move the cursor to the first <product> element
while ($reader->read() && $reader->name !== 'product') {
}

while ($reader->name === 'product') {
    $product = simplexml_import_dom($doc->importNode($reader->expand(), true));

    $processed++;
    if ((int) $product->stock < 10) {
        $lowStock[] = "{$product['sku']} {$product->name} ({$product->stock} left)";
    }

    $reader->next('product');   // jump to the next <product>, skipping the current subtree
}

$reader->close();

echo "Products processed: $processed\n";
echo "Low stock: " . implode(', ', $lowStock) . "\n";

Output

Products processed: 3
Low stock: LAMP-01 Desk Lamp (6 left)

Step-by-Step Explanation

  1. The first loop reads nodes until the cursor reaches the first <product> element.
  2. expand() builds a DOM node for the current element only — one product with its children — not the whole document.
  3. importNode() adds it to a small helper document, and simplexml_import_dom() converts it into a SimpleXMLElement, so the familiar $product->stock and $product['sku'] syntax works.
  4. next('product') moves directly to the next sibling <product>, skipping the current product's inner nodes. When there are no more products, the cursor moves past them and the loop ends.
  5. Memory use depends only on the size of one product, regardless of how many products the file contains.

In a real import, each product would be validated and inserted into the database (in batches, inside a transaction) instead of collected in an array.


Handling Errors

XMLReader reads lazily, so errors in a malformed document appear only when the cursor reaches them — possibly after many records were already processed:

<?php
libxml_use_internal_errors(true);

$reader = new XMLReader();
$reader->XML('<catalog><product sku="A"/><product sku="B"></catalog>');

$skus = [];
while (@$reader->read()) {
    if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'product') {
        $skus[] = $reader->getAttribute('sku');
    }
}

$errors = libxml_get_errors();
echo "Read before failing: " . implode(', ', $skus) . "\n";
echo $errors === [] ? "Document OK\n" : "Error: " . trim($errors[0]->message) . "\n";
libxml_clear_errors();

Output

Read before failing: A, B
Error: Opening and ending tag mismatch: product line 1 and catalog

Both products were read before the error was detected at the closing tag. For imports, this means: process inside a database transaction and roll back if an error occurs, or validate the file first. XMLReader can also validate while reading against an XML Schema (setSchema()) or a RELAX NG schema (setRelaxNGSchema()).


When to Use XMLReader

Use XMLReaderUse Something Else
Large feeds, exports, and imports (megabytes to gigabytes)Small documents → SimpleXML
Reading only specific records or values from huge filesComplex queries across the whole document → DOM with XPath
Constant memory in long-running workersWriting XML → XMLWriter

Common Mistakes

Calling expand() on the Wrong Node

Effect: expand() on a text or end-element node returns nothing useful.

Fix: Expand only when nodeType is XMLReader::ELEMENT and the name matches.

Using read() Instead of next() After Processing a Record

Effect: The loop walks through every child node of the record you already processed.

Fix: Use next('elementName') to skip to the next sibling.

Forgetting Self-Closing Elements

Effect: Code waiting for an END_ELEMENT never sees one for <product/>.

Fix: Check isEmptyElement.

Assuming the Whole File Is Valid Before Processing

Effect: Partial imports when an error appears late in the file.

Fix: Use transactions or validate first.


Best Practices

  • Use XMLReader for large XML files and streams.
  • Jump between records with next() and process each with expand() plus SimpleXML or DOM.
  • Collect libxml errors and treat imports as all-or-nothing operations.
  • Validate against schemas where the format defines one.
  • Release resources with close() in long-running scripts.

Official reference: https://www.php.net/manual/en/book.xmlreader.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro
Ask about this post
AI Ask about this post

Ask questions about PHP XMLReader and get answers drawn from it.

Signed-in readers only.