XmlDocument Class

XmlDocument is the XML document of the library. It has a PHP DOMDocument inside (getDomDocument() gives it), and adds what is needed to work with XML as data: the encoding of the document, access to its metadata, serialization that handles special characters, canonicalization, XPath queries and conversion to an array.

It is the document that XmlEncoder::encode() gives, the one that XmlDecoder::decode() and XmlValidator::validate() receive, and the one that the exceptions carry.

Key Features

  • Loading with the errors of libxml in the exception.
  • The encoding of the document, to read and to change.
  • Access to the metadata of the document: the name of the document element, its namespace and its schema.
  • Serialization with the special characters and entities handled.
  • Canonical XML (C14N), also in the encoding of the document.
  • XPath queries with parameters.
  • Conversion to a simple array, and to JSON.

Basic Usage

Creating a New Document

use Derafu\Xml\XmlDocument;

// A document that is empty: UTF-8 and version 1.0.
$document = new XmlDocument();

// Or with another encoding (the first parameter) and version (the second one).
$document = new XmlDocument('ISO-8859-1', '1.0');

The encoding and the version are the ones of the XML declaration when the document is saved. A document that is loaded has the ones that the XML declares.

The document formats the XML that it saves (with line breaks and indentation). It can be changed:

// Save without adding line breaks nor indentation.
$document->setFormatOutput(false);

// Before loading: take out the spaces between elements of the XML that is loaded.
$document->setPreserveWhiteSpace(false);

The spaces that an XML that is loaded already has between its elements are kept, unless setPreserveWhiteSpace(false) is called before loadXml().

Loading XML Content

// Load from a string.
$xmlString = '<root><element>value</element></root>';
$document->loadXml($xmlString);

loadXml() returns true. If the string is empty or the XML is not well formed it throws an XmlParseException that has the errors of libxml (see Exceptions):

use Derafu\Xml\Exception\XmlParseException;

try {
    $document->loadXml('<root><element></root>');
} catch (XmlParseException $e) {
    echo $e->getMessage(); // "Failed to load the XML: Line 1, column 23: Opening and ending tag mismatch: element (line 1) and root"
    $errors = $e->getErrors(); // One XmlError for each error of libxml.
}

The loading does not convert the encoding: the document has the one that the XML declares, and getEncoding() says it (in upper case; UTF-8 if the XML does not declare one). External entities are not loaded.

Accessing Document Information

// The name of the document element, with its prefix if it has one.
$name = $document->getName(); // e.g., "root"

// The default namespace of the document element (`xmlns`), if it has one.
$namespace = $document->getNamespace(); // e.g., "urn:example:doc"

// The path of the schema in xsi:schemaLocation, if there is one.
$schema = $document->getSchema(); // e.g., "/path/to/schema.xsd"

// The element itself, to work with it as a DOMElement.
$element = $document->getDocumentElement(); // null if there is no content.

// The same element, for who needs it: there is no null to check.
$element = $document->requireDocumentElement(); // XmlException if there is no content.

requireDocumentElement() is the one to use when the code can not go on without the element. A document without content is not a value that is not there, it is a mistake of whoever asks, so it is an XmlException and not a null that would have to be checked before using it. getName(), getNamespace() and getSchema() use it: they throw an XmlException if the document has no document element (it has no content yet), because there is nothing to ask.

For xsi:schemaLocation="urn:example:doc /path/to/schema.xsd", getSchema() gives the second part: /path/to/schema.xsd.

The Encoding

$encoding = $document->getEncoding(); // e.g., "ISO-8859-1"

// Change the encoding of the XML that is saved.
$document->setEncoding('ISO-8859-1');

The characters that the encoding can not write are saved as character references (&#8364;).

Saving and Serializing

// The complete XML document with the declaration.
$xmlString = $document->saveXml();

// The XML without the declaration and without initial or final line breaks.
$xmlContent = $document->getXml();

Both handle the special characters of the text: &, < and > are escaped, and the quotes are written as &quot; and &apos;.

$document->loadXml('<root><element>Text with &amp; &lt; &gt; " \'</element></root>');
$document->getXml();
// <root>
//   <element>Text with &amp; &lt; &gt; &quot; &apos;</element>
// </root>

Canonical XML

// Canonical XML (C14N). It is always in UTF-8.
$canonical = $document->C14N();

// The same, in the encoding that the document declares (UTF-8 if it has none).
$encoded = $document->C14NEncoded();

// Only a part of the document, the first node that an XPath finds.
$part = $document->C14NEncoded('/root/section');

// The same, without the spaces between the tags.
$flat = $document->C14NEncodedFlattened();
$flatPart = $document->C14NEncodedFlattened('/root/section');
  • C14N() takes the parameters of the DOMDocument::C14N() method of PHP (exclusive, with comments, list of nodes and namespace prefixes).
  • C14NEncoded() and C14NEncodedFlattened() throw an XmlQueryException if the XPath finds no node.
  • If the XML can not be canonicalized, C14N(), C14NEncoded(), C14NEncodedFlattened() and getTopLevelSignatureC14N() throw an XmlException, with what libxml said in getErrors(). It happens, for example, when a namespace is a relative URI (<a xmlns="relative">): libxml loads it, but the Canonical XML recommendation requires the canonicalization to fail. PHP gives false for it; the library does not.

To canonicalize a node, and not the whole document, XmlHelper::canonicalize() takes the same parameters, with the node (or the DOMDocument) first, and fails in the same way:

use Derafu\Xml\XmlHelper;

$node = $document->getNodes('/root/section')->item(0);
$canonical = XmlHelper::canonicalize($node, exclusive: true);

XPath Querying

The document has XPath queries with the namespaces ignored (see XPath Query for the details and to query with namespaces):

// The result as a string, an array or null.
$result = $document->query('/root/element');

// The result as a DOMNodeList.
$nodes = $document->getNodes('/root/element');

// With parameters, quoted for you.
$result = $document->query('/root/element[@id=:id]', ['id' => '123']);

Array Conversion

toArray() is the simple version of the conversion: the value of each element by its name, without the attributes, the namespaces nor the comments. A document without content is an empty array. To have all of the XML, to create it again from the array for example, use the decoder (see Array Structure).

// Convert the entire document to an array.
$array = $document->toArray();

// Access a specific part using dot notation.
$value = $document->get('root.element');

// With a default value if not found.
$value = $document->get('root.missing', 'default value');

// The document is the array in JSON.
$json = json_encode($document);

Working with Signatures

If the document has a Signature element as a child of its document element, you can get its canonicalized XML (C14N):

// Get the canonicalized XML of the Signature that is a child of the document element.
$signatureXml = $document->getTopLevelSignatureC14N();

// Returns null if the document element has no Signature as a child.
if ($signatureXml !== null) {
    // Process signature...
}

The Signature is found by its local name, in any namespace or prefix, and only among the children of the document element. The result is the one that C14N gives (in UTF-8, whatever the encoding of the document is), with the declarations of the namespaces that are in scope in the Signature. For a Signature that is deeper in the document, for example inside an extension, use C14NEncoded() with its XPath.

Serializing the Document

The document can be serialized with serialize() and restored with unserialize(): its XML is what is kept, so it comes back with the same content and encoding, and it works the same.

$copy = unserialize(serialize($document));

Performance Considerations

  • With large XML documents, prefer XPath queries to extract the part that you need rather than converting the entire document to an array.
  • The canonicalization methods do more work: use them when you need the canonical XML.
  • getXml() is saveXml() without the declaration, so it does a little more work, not less.
On this page

Last updated on 30/10/2026 by Anonymous