PHP XML Expat
Learn the PHP Expat XML parser: an event-based, streaming way to parse XML with handler callbacks, attribute reading, error checking, and runnable examples.
PHP ships with a built-in XML parser based on the Expat library. Unlike tree-based parsers such as SimpleXML or the DOM, Expat is an event-based (or "SAX-style") parser: instead of loading the whole document into memory as a tree, it streams through the XML and fires your callback functions as it encounters each start tag, end tag, and chunk of text.
This page explains how Expat works, when you should reach for it, and walks through a complete, runnable example with attribute handling and error checking.
What is the Expat parser?
Expat is a fast, lightweight, non-validating XML parser written in C. The PHP xml extension wraps it through the xml_parser_* family of functions. Its defining traits:
- Event-based — you register handler callbacks; the parser calls them as it reads.
- Streaming — XML can be fed to it in pieces (
xml_parse()can be called repeatedly), so memory use stays low even for huge files. - Non-validating — it checks that the document is well-formed, but does not validate against a DTD or schema.
This is the opposite trade-off from a tree parser. A tree parser is convenient (you can navigate the whole document with $xml->note->message) but holds everything in memory. Expat keeps memory flat at the cost of forcing you to track state as the events stream by.
When should you use Expat?
- Large documents — multi-megabyte feeds or exports where building a full DOM tree would be wasteful.
- Streaming sources — data arriving over a socket or in chunks, where you can't wait for the whole document.
- Extract-and-discard — you only need a few values and don't want the overhead of a tree.
For small documents you simply want to read, SimpleXML is far less code. See Types of PHP XML parsers for a side-by-side comparison.
Setting up the extension
The xml extension is bundled with PHP and enabled by default in most builds. If parser functions are missing, enable it in php.ini:
extension=xml ; Linux/macOS
extension=php_xml.dll ; WindowsYou can confirm it is available at runtime:
<?php
var_dump(function_exists('xml_parser_create')); // bool(true)A complete Expat example
The example below parses an XML string and prints each event. It registers three handlers — for start tags, end tags, and text — and finishes with proper error checking. Reading from a string keeps it self-contained; the file-based variant follows.
<?php
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<note importance="high">
<to>User</to>
<from>System</from>
<message>Hello from Expat</message>
</note>
XML;
// 1. Create the parser.
$parser = xml_parser_create();
// Keep tag names in their original case (Expat upper-cases them by default).
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
// 2. Register handlers for start/end tags and for character data.
xml_set_element_handler(
$parser,
function ($parser, $name, $attrs) {
echo "Start: $name";
foreach ($attrs as $key => $value) {
echo " [$key=$value]";
}
echo "\n";
},
function ($parser, $name) {
echo "End: $name\n";
}
);
xml_set_character_data_handler($parser, function ($parser, $data) {
$data = trim($data); // text between tags includes whitespace
if ($data !== '') {
echo "Text: $data\n";
}
});
// 3. Feed the document to the parser (true = this is the final chunk).
if (!xml_parse($parser, $xml, true)) {
$code = xml_get_error_code($parser);
die(sprintf(
"XML error: %s at line %d\n",
xml_error_string($code),
xml_get_current_line_number($parser)
));
}
// 4. Release the parser.
xml_parser_free($parser);Output:
Start: note [importance=high]
Start: to
Text: User
End: to
Start: from
Text: System
End: from
Start: message
Text: Hello from Expat
End: message
End: noteHow it reads. xml_parser_create() builds the parser. xml_set_element_handler() (reference) registers the start- and end-tag callbacks, and xml_set_character_data_handler() (reference) registers the text callback. xml_parse() then drives the whole thing, calling those functions in document order. The start handler receives an $attrs array, so reading an attribute like importance is just $attrs['importance'].
Two details that trip people up:
- Whitespace is character data. The newlines and indentation between tags fire the character-data handler too — that's why the example calls
trim()and skips empty strings. - Case folding. By default Expat upper-cases element names.
XML_OPTION_CASE_FOLDINGset tofalsekeeps the original casing, which is almost always what you want.
Parsing a file in chunks
The real strength of Expat is streaming. Read the file a block at a time and feed each block to xml_parse(), passing true only on the final chunk (detected with feof()):
<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, 'startElement', 'endElement');
xml_set_character_data_handler($parser, 'characterData');
$fp = fopen('example.xml', 'r') or die("Could not open file\n");
while ($data = fread($fp, 4096)) {
if (!xml_parse($parser, $data, feof($fp))) {
$code = xml_get_error_code($parser);
die(sprintf(
"XML error: %s at line %d\n",
xml_error_string($code),
xml_get_current_line_number($parser)
));
}
}
fclose($fp);
xml_parser_free($parser);
function startElement($parser, $name, $attrs) { echo "Start: $name\n"; }
function endElement($parser, $name) { echo "End: $name\n"; }
function characterData($parser, $data) {
$data = trim($data);
if ($data !== '') echo "Text: $data\n";
}Because the document is never fully held in memory, this pattern handles files far larger than your memory limit. Handlers can be plain function names (as here), closures, or [$object, 'method'] callables via xml_set_object().
Handling errors
Always check the return value of xml_parse() — it returns false on a malformed document. The error code from xml_get_error_code() can be turned into a readable message with xml_error_string(), and you can pinpoint the location with xml_get_current_line_number() and xml_get_current_column_number(). Skipping this check means a broken feed fails silently.
Advantages of Expat
- Low memory — streams the document instead of building a tree, so memory stays flat regardless of file size.
- Fast — the underlying C parser is highly optimized.
- Cross-platform — bundled with PHP on every supported OS.
- Fine-grained control — you decide exactly what to do at each event, ignoring everything you don't need.
The trade-off: because there is no tree, you must track context (which element you're inside) yourself. If that bookkeeping gets heavy, a tree parser like SimpleXML or DOM is the better fit.
Conclusion
The Expat-based XML parser gives PHP a fast, memory-efficient, event-driven way to read XML. Register handlers for the events you care about, feed the document with xml_parse(), check for errors, and free the parser when done. Reach for it on large or streaming documents; for small ones, SimpleXML is usually the simpler choice.