xml_set_external_entity_ref_handler()
The xml_set_external_entity_ref_handler() function is a PHP built-in function that sets a user-defined function as the handler for external entity references in
The xml_set_external_entity_ref_handler() function is a PHP built-in function that registers a user-defined callback to handle external entity references in a legacy SAX (Expat) XML parser. An external entity is a reference inside an XML document — declared with <!ENTITY name SYSTEM "uri"> — that points to content stored outside the document. When the parser meets such a reference during parsing, it invokes your callback so you can decide what to do with it: ignore it, load approved data from a database, or reject it as part of security validation.
This page covers the function's syntax, the parameters PHP passes to your callback, its return value, a complete runnable example, and the security and deprecation caveats you need to know before using it.
Syntax
xml_set_external_entity_ref_handler(XMLParser $parser, callable $handler): boolParameters
| Parameter | Description |
|---|---|
$parser | The XML parser resource created with xml_parser_create(). The handler is attached to this specific parser. |
$handler | The callback to run on each external entity reference. It can be the name of a function (as a string), or — when xml_set_object() was used — a method name. Passing an empty string unsets the handler. |
Return value
Returns true on success, or false on failure (for example, if $parser is not a valid parser).
The callback signature
PHP calls your handler with five arguments, in this order:
handler(XMLParser $parser, string $open_entity_names, string $base, string $system_id, ?string $public_id): int$open_entity_names— a space-separated list of the entities that are currently open, used to detect recursion.$base— the base URI for resolving$system_id(usually an empty string).$system_id— the system identifier (theSYSTEM "..."URI) of the external entity.$public_id— the public identifier, ornullif none was declared.
Your callback must return a non-zero (truthy) value to keep the parse going. Returning 0, false, or nothing aborts the parse with an XML_ERROR_EXTERNAL_ENTITY_HANDLING error.
Usage Examples
Let's take a look at a practical example of using xml_set_external_entity_ref_handler() in PHP.
Example: Setting an External Entity Reference Handler Function
Suppose you have an XML document that references an external entity and you want to inspect that reference as the document is parsed. You create a parser with xml_parser_create(), register the handler, parse the data with xml_parse(), and free the parser with xml_parser_free():
Setting an External Entity Reference Handler Function in PHP
function handle_external_entity_ref($parser, $open_entity_names, $base, $system_id, $public_id) {
// Inspect — but do NOT blindly load — the external entity.
echo "External entity referenced: {$system_id}\n";
// Return a non-zero value so parsing continues.
return 1;
}
$xml_parser = xml_parser_create();
xml_set_external_entity_ref_handler($xml_parser, "handle_external_entity_ref");
$xml_data = '<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY ext SYSTEM "data.xml">
]>
<root>&ext;</root>';
xml_parse($xml_parser, $xml_data, true);
xml_parser_free($xml_parser);The handler fires once for the &ext; reference and prints:
External entity referenced: data.xmlBecause the callback only echoes the system identifier and returns 1, the parser is told to continue without actually fetching data.xml — which is exactly the safe default you want.
Why the handler may never fire
In most modern PHP builds, external entity loading is disabled at the libxml level, so the parser silently ignores entity references and your callback is never called. This is intentional hardening. If you must opt in, you control it globally with libxml_disable_entity_loader() — but for anything but trusted, controlled input you should leave loading disabled.
⚠️ Security Warning: Handling external entities is a classic vector for XML External Entity (XXE) attacks, which can leak local files (
file:///etc/passwd), trigger server-side requests, or cause denial of service. Never resolve$system_idto fetch arbitrary URIs or local paths from untrusted input. For security-sensitive parsing, prefer modern libraries such asDOMDocumentorXMLReaderwith entity loading turned off.
Deprecation note: As of PHP 8.4, passing a non-callable string as the handler is deprecated — a plain function name that actually exists still works, but an unresolved string now raises a deprecation notice. For forward-compatible code, pass a real callable such as a
Closureor a[$object, 'method']array. The legacy Expat SAX extension as a whole is in maintenance mode — new code should favorXMLReaderorDOMDocument.
Conclusion
In this article, we've covered PHP's xml_set_external_entity_ref_handler() function: its syntax, the five arguments PHP passes to your callback, why the callback must return a non-zero value to keep parsing alive, and a complete runnable example. We've also flagged the two things that trip people up — the handler often never fires because external entity loading is disabled by default, and routing untrusted input through it opens the door to XXE attacks. Use it only with trusted input, prefer a real callable over a string name on PHP 8.4+, and reach for XMLReader or DOMDocument for anything security-sensitive.
For related SAX handlers, see xml_set_element_handler() and xml_set_object().