libxml_set_external_entity_loader()
Today, we will discuss the libxml_set_external_entity_loader() function in PHP. This function is used to set a custom function to load external entities in XML
The libxml_set_external_entity_loader() function registers a custom callback that PHP's libxml-based parsers (DOMDocument, SimpleXML, XMLReader) call whenever an XML document tries to pull in an external entity — a file, URL, or DTD referenced from inside the markup. By controlling that callback you decide which external resources may be loaded and which are blocked, which is the standard, future-proof way to defend against XML External Entity (XXE) attacks. This page explains what external entities are, why they are dangerous, the function's signature on PHP 8, and how to write a safe loader with working examples.
What Is an External Entity (and Why XXE Is Dangerous)?
An external entity is a placeholder declared in a document type definition (DTD) that resolves to content from outside the XML document. For example:
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>When a parser expands &secret;, it reads /etc/passwd and injects the file's contents into the document. An attacker who controls the XML input can therefore read local files, reach internal network endpoints (SSRF), or trigger denial-of-service "billion laughs" expansion. Blocking — or tightly whitelisting — external entity loading shuts this down.
What Is the libxml_set_external_entity_loader() Function?
libxml_set_external_entity_loader() is a built-in PHP function that registers a single global callback. From the moment it is set, every external-entity request from any libxml parser passes through your callback first. Returning null from the callback tells libxml to skip the entity; returning the entity content (as a string or an open resource) lets it load. It was introduced in PHP 5.1.0 and remains the recommended approach because the older libxml_disable_entity_loader() is deprecated in PHP 8.0 and is largely unnecessary in modern PHP (external entities are not loaded by default since libxml 2.9 / PHP 8.0).
Syntax
libxml_set_external_entity_loader(?callable $resolver_function): voidPassing null removes a previously registered loader and restores the default behavior.
Parameters
| Parameter | Description |
|---|---|
resolver_function | A callable, or null to reset. The callback receives three arguments and must return the entity content (a string), an open resource, or null to block loading. |
The callback signature on PHP 8.0+ is:
function (?string $public_id, ?string $system_id, array $context): string|resource|null$public_id— the entity's public identifier (oftennull).$system_id— the URI/path the entity points to (e.g.file:///etc/passwd).$context— an array with keys such asdirectory,intSubName,extSubURI, andextSubSystemdescribing the parse context.
Return Value
libxml_set_external_entity_loader() itself returns void (it returned true prior to PHP 8.0).
How to Use libxml_set_external_entity_loader()
Define a callback, register it, then parse. The simplest safe policy is to block everything by always returning null:
<?php
// Block every external entity request.
libxml_set_external_entity_loader(
static fn (?string $publicId, ?string $systemId, array $context): ?string => null
);
$xml = <<<'XML'
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>
XML;
$doc = new DOMDocument();
$doc->loadXML($xml);
// The entity was blocked, so it expands to nothing.
echo "Loaded value: [" . $doc->documentElement->textContent . "]\n";
echo "Done without reading any external file.\n";
?>The &secret; reference resolves to an empty string because our loader returned null, so /etc/passwd is never read.
Whitelisting Trusted Sources
If your application legitimately needs some external entities (for example a local DTD that ships with your code), allow only the paths you trust and reject everything else:
<?php
function trusted_entity_loader(?string $publicId, ?string $systemId, array $context)
{
// Only allow files inside our own schema directory.
$allowedDir = __DIR__ . '/schemas/';
if ($systemId === null) {
return null;
}
$path = str_starts_with($systemId, 'file://')
? substr($systemId, 7)
: $systemId;
$real = realpath($path);
if ($real !== false && str_starts_with($real, realpath($allowedDir))) {
return file_get_contents($real); // Trusted: load it.
}
return null; // Everything else is blocked.
}
libxml_set_external_entity_loader('trusted_entity_loader');
echo "Loader registered: only ./schemas/ files may be resolved.\n";
?>Here any systemId that does not resolve to a real file inside schemas/ returns null and is blocked, while trusted local schema files load normally.
Note: The loader is global and applies to every libxml parse in the request. Reset it with
libxml_set_external_entity_loader(null)when you are done if other code in the same request relies on default behavior.
Common Mistakes and Gotchas
- Returning the wrong type. Return a
string, an openresource, ornull— returningfalseortrueis not valid and can raise aTypeErroron PHP 8. - Forgetting that it is global. The last registered loader wins for the whole request; libraries that set their own loader can override yours.
- Assuming you still need
libxml_disable_entity_loader(). On PHP 8+ external entities are off by default; use this function only when you need custom control over loading. - Trusting
$systemIdblindly. Always validate the path/URL before reading it, or you reintroduce the XXE/SSRF hole you were trying to close.
Conclusion
libxml_set_external_entity_loader() gives you a single choke point for every external entity a libxml parser tries to load, making it the modern, recommended defense against XXE and SSRF in PHP XML processing. Block everything by returning null, or whitelist only the local resources you trust. For the broader libxml toolkit, see the related functions below.
See Also
- PHP libxml — overview of the libxml extension and its constants.
- libxml_disable_entity_loader() — the older, deprecated way to turn off entity loading.
- PHP XML DOM — parsing XML with
DOMDocument.