Aug 2, 2017 9:00:43 AM | PHP Exception Handling - DOMException

A close look into the PHP DOMException class, including code samples illustrating how to handle common DOMException scenarios yourself.

Next up in our in-depth PHP Exception Handling series, today we're digging into the PHP DOMException error. There are a variety of reasons a DOMException can occur, but all of them are related to using the Document Object Model namespace and its powerful functionality.

In this article we'll examine the DOMException in more detail, including where it sits in the PHP Exception Hierarchy, and how DOMExceptions might be commonly thrown using some functional sample code, so let's get to it!

The Technical Rundown

  • All PHP errors implement the Throwable interface, or are extended from another inherited class therein.
  • Exception implements the Throwable interface.
  • DOMException extends the Exception class.

When Should You Use It?

Let's jump right into it by looking at the full working code sample we have, which features two kinds of DOMExceptions that might typically occur. Following the sample below, we'll break the code down in more detail to see exactly what's going on:

<?php

function executeExamples()
{
// Create document.
$document = new DOMDocument('1.0');

// Append 'books' element.
appendElementToDocument(new DOMElement('books'), $document);

Logging::LineSeparator();

// Append '


<code class="php"></code>

Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

<code class="php"></code>
/**
* Create and append DOMElement, by name, to passed DOMDocument.
*
* @param string $name
* @param DOMDocument $document
* @return DomNode|null
*/
function appendElementByNameToDocument(string $name, DOMDocument $document) : ?DomNode {
try {
$node = $document->appendChild(new DOMElement($name));
Logging::Log("Successfully appended element (by name) [{$node->localName}] to [document].");
return $node;
} catch (DOMException $exception) {
// Output expected DOMException.
Logging::Log($exception);
return null;
} catch (Exception $exception) {
// Output unexpected Exceptions.
Logging::Log($exception, false);
return null;
}
}

/**
* Append passed DOMElement to passed DOMDocument.
*
* @param DOMElement $element
* @param DOMDocument $document
* @return DOMNode|null
*/
function appendElementToDocument(DOMElement $element, DOMDocument $document) : ?DOMNode {
try {
$node = $document->appendChild($element);
Logging::Log("Successfully appended element [{$node->localName}] to [document].");
return $node;
} catch (DOMException $exception) {
// Output expected DOMException.
Logging::Log($exception);
return null;
} catch (Exception $exception) {
// Output unexpected Exceptions.
Logging::Log($exception, false);
return null;
}
}

<code class="php"></code>

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

<code class="php"></code>

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

<code class="php"></code>
// Create document.
$document = new DOMDocument('1.0');

// Append 'books' element.
appendElementToDocument(new DOMElement('books'), $document);

<code class="php"></code>

This works just as expected and the output confirms the result:

<code class="php"></code>
Successfully appended element [books] to [document].
<code class="php"></code>

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

<code class="php"></code>
<code class="php">// Append '</code>

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

<code class="php"></code>
[EXPECTED] DOMException: Invalid Character Error in D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php on line 36
<code class="php"></code>

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

<code class="php"></code>
/**
* Append element to unattached document.
*/
function crossDocumentAppendTest() {
try {
// Create document and element set A.
$documentA = new DOMDocument('1.0');
$elementA = new DOMElement('elementA');
appendElementToDocument($elementA, $documentA);

// Create document and element set B.
$documentB = new DOMDocument('1.0');
$elementB = new DOMElement('elementB');
appendElementToDocument($elementB, $documentB);

// Append elementA (appended to documentA) to documentB.
appendElementToDocument($elementA, $documentB);

Logging::Log("Successfully appended element [{$elementA->localName}] to [documentB].");
} catch (DOMException $exception) {
// Output expected DOMException.
Logging::Log($exception);
} catch (Exception $exception) {
// Output unexpected Exceptions.
Logging::Log($exception, false);
}
}

<code class="php"></code>

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

<code class="php"></code>
Successfully appended element [elementA] to [document].
Successfully appended element [elementB] to [document].
[EXPECTED] DOMException: Wrong Document Error in D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php on line 59

Call Stack:
0.0971 375608 1. {main}() D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php:0
0.1082 1787768 2. executeExamples() D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php:135
1.3642 1792352 3. crossDocumentAppendTest() D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php:20
3.7300 1810528 4. appendElementToDocument(???, ???) D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php:89
9.8372 1814896 5. DOMDocument->appendChild(???) D:\work\Airbrake.io\Exceptions\PHP\Exception\DOMException\code.php:59

<code class="php"></code>

We solve this issue within the crossDocumentAppendTestSuccess() method:

<code class="php"></code>
/**
* Successfully appends element to unattached document.
*/
function crossDocumentAppendTestSuccess() {
try {
// Create document and element set A.
$documentA = new DOMDocument('1.0');
$elementA = new DOMElement('elementA');
appendElementToDocument($elementA, $documentA);

// Create document and element set B.
$documentB = new DOMDocument('1.0');
$elementB = new DOMElement('elementB');
appendElementToDocument($elementB, $documentB);

// Append elementA (appended to documentA) to documentB.
$node = appendElementToDocument($elementA, $documentB);

// Check if appendElementToDocument returned DOMNode or null.
if (!$node) {
// If $node not found, import elementA into documentB, then try appending again.
$importedNode = $documentB->importNode($elementA, true);
$node = appendElementToDocument($importedNode, $documentB);
Logging::Log("Successfully appended element [{$node->localName}] to [documentB].");
}
} catch (DOMException $exception) {
// Output expected DOMException.
Logging::Log($exception);
} catch (Exception $exception) {
// Output unexpected Exceptions.
Logging::Log($exception, false);
}
}

<code class="php"></code>

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

<code class="php"></code>

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

<code class="php"></code>

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

<code class="php"></code>
Successfully appended element [elementA] to [documentB].
<code class="php"></code>

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

<code class="php">element. appendElementByNameToDocument('</code>

<code class="php"></code>

Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

<code class="php"></code>
 
<code class="php"></code>

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

<code class="php"></code>

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

<code class="php"></code>
 
<code class="php"></code>

This works just as expected and the output confirms the result:

<code class="php"></code>
 
<code class="php"></code>

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

<code class="php"></code>
 
<code class="php"></code>

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

<code class="php"></code>
 
<code class="php"></code>

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

<code class="php"></code>
 
<code class="php"></code>

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

<code class="php"></code>
 
<code class="php"></code>

We solve this issue within the crossDocumentAppendTestSuccess() method:

<code class="php"></code>
 
<code class="php"></code>

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

<code class="php"></code>

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

<code class="php"></code>

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

<code class="php"></code>
 
<code class="php"></code>

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

<code class="php">, $document); Logging::LineSeparator(); crossDocumentAppendTest(); Logging::LineSeparator(); crossDocumentAppendTestSuccess(); } /** * Create and append DOMElement, by name, to passed DOMDocument. * * @param string $name * @param DOMDocument $document * @return DomNode|null */ function appendElementByNameToDocument(string $name, DOMDocument $document) : ?DOMNode { try { $node = $document-&gt;appendChild(new DOMElement($name)); Logging::Log("Successfully appended element (by name) [{$node-&gt;localName}] to [document]."); return $node; } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); return null; } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); return null; } } /** * Append passed DOMElement to passed DOMDocument. * * @param DOMElement $element * @param DOMDocument $document * @return DOMNode|null */ function appendElementToDocument(DOMElement $element, DOMDocument $document) : ?DOMNode { try { $node = $document-&gt;appendChild($element); Logging::Log("Successfully appended element [{$node-&gt;localName}] to [document]."); return $node; } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); return null; } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); return null; } } /** * Append element to unattached document. */ function crossDocumentAppendTest() { try { // Create document and element set A. $documentA = new DOMDocument('1.0'); $elementA = new DOMElement('elementA'); appendElementToDocument($elementA, $documentA); // Create document and element set B. $documentB = new DOMDocument('1.0'); $elementB = new DOMElement('elementB'); appendElementToDocument($elementB, $documentB); // Append elementA (appended to documentA) to documentB. appendElementToDocument($elementA, $documentB); Logging::Log("Successfully appended element [{$elementA-&gt;localName}] to [documentB]."); } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); } } /** * Successfully appends element to unattached document. */ function crossDocumentAppendTestSuccess() { try { // Create document and element set A. $documentA = new DOMDocument('1.0'); $elementA = new DOMElement('elementA'); appendElementToDocument($elementA, $documentA); // Create document and element set B. $documentB = new DOMDocument('1.0'); $elementB = new DOMElement('elementB'); appendElementToDocument($elementB, $documentB); // Append elementA (appended to documentA) to documentB. $node = appendElementToDocument($elementA, $documentB); // Check if appendElementToDocument returned DOMNode or null. if (!$node) { // If $node not found, import elementA into documentB, then try appending again. $importedNode = $documentB-&gt;importNode($elementA, true); $node = appendElementToDocument($importedNode, $documentB); Logging::Log("Successfully appended element [{$node-&gt;localName}] to [documentB]."); } } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); } } executeExamples(); // Logging.php &lt;?php require('kint.php'); /** * Provides basic logging/output functionality. */ class Logging { /** * Logs the passed object, string, or Throwable instance to the console. * * @param object|string $a Message or value to be logged. * @param object|bool $b Secondary value, such as boolean for Throwables indicating if error was expected. */ public static function Log($a, $b = null) { if (is_string($a) || is_numeric($a)) { Logging::LogString($a); } elseif ($a instanceof Throwable) { Logging::LogThrowable($a, is_null($b) ? true : $b); } else { Logging::LogObject($a); } } /** * Logs the passed object. * * @param object $object Object to be logged. * * @see https://github.com/kint-php/kint Kint tool used for structured outputs. */ private static function LogObject(object $object) { Kint_Renderer_Cli::$force_utf8 = true; Kint_Renderer_Text::$decorations = false; Kint::dump($object); } /** * Logs the passed string value. * * @param string $value Value to be logged. */ private static function LogString(string $value) { print_r("{$value}n"); } /** * Logs the passed Throwable object. * Includes message, className if error was expected, and stack trace. * * Uses internal Reflection to retrieve protected/private properties. * * @param Throwable $throwable Throwable object to be output. * @param bool $expected Indicates if error was expected or not. */ private static function LogThrowable(Throwable $throwable, bool $expected = true) { $expected = $expected ? "EXPECTED" : "UNEXPECTED"; $message = substr($throwable-&gt;xdebug_message, 1); // Output whether error was expected or not, the class name, the message, and stack trace. print_r("[{$expected}] {$message}n"); // Add line separator to keep it tidy. self::LineSeparator(); } /** * Outputs a separator line to log. * * @param int $length Length of the line separator. * @param string $character Character to use as separator. */ public static function LineSeparator(int $length = 40, string $character = '-') { $break = str_repeat($character, $length); print_r("{$break}n"); } } </code>
<code class="php"></code>

<code class="php"></code>

Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

<code class="php"></code>
 
<code class="php"></code>

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

<code class="php"></code>

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

<code class="php"></code>
 
<code class="php"></code>

This works just as expected and the output confirms the result:

<code class="php"></code>
 
<code class="php"></code>

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

<code class="php"></code>
 
<code class="php"></code>

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

<code class="php"></code>
 
<code class="php"></code>

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

<code class="php"></code>
 
<code class="php"></code>

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

<code class="php"></code>
 
<code class="php"></code>

We solve this issue within the crossDocumentAppendTestSuccess() method:

<code class="php"></code>
 
<code class="php"></code>

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

<code class="php"></code>

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

<code class="php"></code>

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

<code class="php"></code>
 
<code class="php"></code>

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

<code class="php">element. appendElementByNameToDocument('</code>

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

<code class="php"></code>
 
<code class="php"></code>

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

<code class="php"></code>
 
<code class="php"></code>

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

<code class="php"></code>
 
<code class="php"></code>

We solve this issue within the crossDocumentAppendTestSuccess() method:

<code class="php"></code>
 
<code class="php"></code>

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

<code class="php"></code>

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

<code class="php"></code>

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

<code class="php"></code>
 
<code class="php"></code>

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

<code class="php">element. appendElementByNameToDocument('</code>

<code class="php"></code>

Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

<code class="php"></code>
 
<code class="php"></code>

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

<code class="php"></code>

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

<code class="php"></code>
 
<code class="php"></code>

This works just as expected and the output confirms the result:

<code class="php"></code>
 
<code class="php"></code>

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

<code class="php"></code>
 
<code class="php"></code>

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

<code class="php"></code>
 
<code class="php"></code>

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

<code class="php"></code>
 
<code class="php"></code>

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

<code class="php"></code>
 
<code class="php"></code>

We solve this issue within the crossDocumentAppendTestSuccess() method:

<code class="php"></code>
 
<code class="php"></code>

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

<code class="php"></code>

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

<code class="php"></code>

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

<code class="php"></code>
 
<code class="php"></code>

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

<code class="php">, $document); Logging::LineSeparator(); crossDocumentAppendTest(); Logging::LineSeparator(); crossDocumentAppendTestSuccess(); } /** * Create and append DOMElement, by name, to passed DOMDocument. * * @param string $name * @param DOMDocument $document * @return DomNode|null */ function appendElementByNameToDocument(string $name, DOMDocument $document) : ?DOMNode { try { $node = $document-&gt;appendChild(new DOMElement($name)); Logging::Log("Successfully appended element (by name) [{$node-&gt;localName}] to [document]."); return $node; } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); return null; } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); return null; } } /** * Append passed DOMElement to passed DOMDocument. * * @param DOMElement $element * @param DOMDocument $document * @return DOMNode|null */ function appendElementToDocument(DOMElement $element, DOMDocument $document) : ?DOMNode { try { $node = $document-&gt;appendChild($element); Logging::Log("Successfully appended element [{$node-&gt;localName}] to [document]."); return $node; } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); return null; } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); return null; } } /** * Append element to unattached document. */ function crossDocumentAppendTest() { try { // Create document and element set A. $documentA = new DOMDocument('1.0'); $elementA = new DOMElement('elementA'); appendElementToDocument($elementA, $documentA); // Create document and element set B. $documentB = new DOMDocument('1.0'); $elementB = new DOMElement('elementB'); appendElementToDocument($elementB, $documentB); // Append elementA (appended to documentA) to documentB. appendElementToDocument($elementA, $documentB); Logging::Log("Successfully appended element [{$elementA-&gt;localName}] to [documentB]."); } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); } } /** * Successfully appends element to unattached document. */ function crossDocumentAppendTestSuccess() { try { // Create document and element set A. $documentA = new DOMDocument('1.0'); $elementA = new DOMElement('elementA'); appendElementToDocument($elementA, $documentA); // Create document and element set B. $documentB = new DOMDocument('1.0'); $elementB = new DOMElement('elementB'); appendElementToDocument($elementB, $documentB); // Append elementA (appended to documentA) to documentB. $node = appendElementToDocument($elementA, $documentB); // Check if appendElementToDocument returned DOMNode or null. if (!$node) { // If $node not found, import elementA into documentB, then try appending again. $importedNode = $documentB-&gt;importNode($elementA, true); $node = appendElementToDocument($importedNode, $documentB); Logging::Log("Successfully appended element [{$node-&gt;localName}] to [documentB]."); } } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); } } executeExamples(); // Logging.php &lt;?php require('kint.php'); /** * Provides basic logging/output functionality. */ class Logging { /** * Logs the passed object, string, or Throwable instance to the console. * * @param object|string $a Message or value to be logged. * @param object|bool $b Secondary value, such as boolean for Throwables indicating if error was expected. */ public static function Log($a, $b = null) { if (is_string($a) || is_numeric($a)) { Logging::LogString($a); } elseif ($a instanceof Throwable) { Logging::LogThrowable($a, is_null($b) ? true : $b); } else { Logging::LogObject($a); } } /** * Logs the passed object. * * @param object $object Object to be logged. * * @see https://github.com/kint-php/kint Kint tool used for structured outputs. */ private static function LogObject(object $object) { Kint_Renderer_Cli::$force_utf8 = true; Kint_Renderer_Text::$decorations = false; Kint::dump($object); } /** * Logs the passed string value. * * @param string $value Value to be logged. */ private static function LogString(string $value) { print_r("{$value}n"); } /** * Logs the passed Throwable object. * Includes message, className if error was expected, and stack trace. * * Uses internal Reflection to retrieve protected/private properties. * * @param Throwable $throwable Throwable object to be output. * @param bool $expected Indicates if error was expected or not. */ private static function LogThrowable(Throwable $throwable, bool $expected = true) { $expected = $expected ? "EXPECTED" : "UNEXPECTED"; $message = substr($throwable-&gt;xdebug_message, 1); // Output whether error was expected or not, the class name, the message, and stack trace. print_r("[{$expected}] {$message}n"); // Add line separator to keep it tidy. self::LineSeparator(); } /** * Outputs a separator line to log. * * @param int $length Length of the line separator. * @param string $character Character to use as separator. */ public static function LineSeparator(int $length = 40, string $character = '-') { $break = str_repeat($character, $length); print_r("{$break}n"); } } </code>

Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

 

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

 

This works just as expected and the output confirms the result:

 

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

 

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

 

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

 

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

 

We solve this issue within the crossDocumentAppendTestSuccess() method:

 

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

 

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

, $document);

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

 

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

 

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

 

We solve this issue within the crossDocumentAppendTestSuccess() method:

 

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

 

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

element. appendElementByNameToDocument('


Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

 

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

 

This works just as expected and the output confirms the result:

 

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

 

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

 

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

 

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

 

We solve this issue within the crossDocumentAppendTestSuccess() method:

 

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

 

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

, $document); Logging::LineSeparator(); crossDocumentAppendTest(); Logging::LineSeparator(); crossDocumentAppendTestSuccess(); } /** * Create and append DOMElement, by name, to passed DOMDocument. * * @param string $name * @param DOMDocument $document * @return DomNode|null */ function appendElementByNameToDocument(string $name, DOMDocument $document) : ?DOMNode { try { $node = $document->appendChild(new DOMElement($name)); Logging::Log("Successfully appended element (by name) [{$node->localName}] to [document]."); return $node; } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); return null; } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); return null; } } /** * Append passed DOMElement to passed DOMDocument. * * @param DOMElement $element * @param DOMDocument $document * @return DOMNode|null */ function appendElementToDocument(DOMElement $element, DOMDocument $document) : ?DOMNode { try { $node = $document->appendChild($element); Logging::Log("Successfully appended element [{$node->localName}] to [document]."); return $node; } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); return null; } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); return null; } } /** * Append element to unattached document. */ function crossDocumentAppendTest() { try { // Create document and element set A. $documentA = new DOMDocument('1.0'); $elementA = new DOMElement('elementA'); appendElementToDocument($elementA, $documentA); // Create document and element set B. $documentB = new DOMDocument('1.0'); $elementB = new DOMElement('elementB'); appendElementToDocument($elementB, $documentB); // Append elementA (appended to documentA) to documentB. appendElementToDocument($elementA, $documentB); Logging::Log("Successfully appended element [{$elementA->localName}] to [documentB]."); } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); } } /** * Successfully appends element to unattached document. */ function crossDocumentAppendTestSuccess() { try { // Create document and element set A. $documentA = new DOMDocument('1.0'); $elementA = new DOMElement('elementA'); appendElementToDocument($elementA, $documentA); // Create document and element set B. $documentB = new DOMDocument('1.0'); $elementB = new DOMElement('elementB'); appendElementToDocument($elementB, $documentB); // Append elementA (appended to documentA) to documentB. $node = appendElementToDocument($elementA, $documentB); // Check if appendElementToDocument returned DOMNode or null. if (!$node) { // If $node not found, import elementA into documentB, then try appending again. $importedNode = $documentB->importNode($elementA, true); $node = appendElementToDocument($importedNode, $documentB); Logging::Log("Successfully appended element [{$node->localName}] to [documentB]."); } } catch (DOMException $exception) { // Output expected DOMException. Logging::Log($exception); } catch (Exception $exception) { // Output unexpected Exceptions. Logging::Log($exception, false); } } executeExamples(); // Logging.php <?php require('kint.php'); /** * Provides basic logging/output functionality. */ class Logging { /** * Logs the passed object, string, or Throwable instance to the console. * * @param object|string $a Message or value to be logged. * @param object|bool $b Secondary value, such as boolean for Throwables indicating if error was expected. */ public static function Log($a, $b = null) { if (is_string($a) || is_numeric($a)) { Logging::LogString($a); } elseif ($a instanceof Throwable) { Logging::LogThrowable($a, is_null($b) ? true : $b); } else { Logging::LogObject($a); } } /** * Logs the passed object. * * @param object $object Object to be logged. * * @see https://github.com/kint-php/kint Kint tool used for structured outputs. */ private static function LogObject(object $object) { Kint_Renderer_Cli::$force_utf8 = true; Kint_Renderer_Text::$decorations = false; Kint::dump($object); } /** * Logs the passed string value. * * @param string $value Value to be logged. */ private static function LogString(string $value) { print_r("{$value}\n"); } /** * Logs the passed Throwable object. * Includes message, className if error was expected, and stack trace. * * Uses internal Reflection to retrieve protected/private properties. * * @param Throwable $throwable Throwable object to be output. * @param bool $expected Indicates if error was expected or not. */ private static function LogThrowable(Throwable $throwable, bool $expected = true) { $expected = $expected ? "EXPECTED" : "UNEXPECTED"; $message = substr($throwable->xdebug_message, 1); // Output whether error was expected or not, the class name, the message, and stack trace. print_r("[{$expected}] {$message}\n"); // Add line separator to keep it tidy. self::LineSeparator(); } /** * Outputs a separator line to log. * * @param int $length Length of the line separator. * @param string $character Character to use as separator. */ public static function LineSeparator(int $length = 40, string $character = '-') { $break = str_repeat($character, $length); print_r("{$break}\n"); } }


Our first goal is to create a new DOMDocument instance, and append a new DOMElement instance to it. To that end, we have two similar methods, appendElementToDocument(DOMElement, DOMDocument) and appendElementByNameToDocument(string, DOMDocument):

 

These methods are comparable, except one expects an actual DOMElement instance to be passed as an argument, while the other uses a passed string $name to create said element. Either way, the element is then appended to the passed DOMDocument using the appendChild() method. If successful, we output a message, and if it fails, we throw (and catch) the exception.

To test these methods out we have just a few lines of code. We start by creating a new DOMDocument instance, then call appendElementToDocument(DOMElement, DOMDocument) with a new element named books:

 

This works just as expected and the output confirms the result:

 

Now let's try the appendElementByNameToDocument(string, DOMDocument) method to create a DOMElement with the name of a dollar sign ($):

 

As it happens, the $ symbol is not a valid character within a DOM element name, so a DOMException is thrown, indicating as much in the message:

 

Our second test involves appending DOMElements to DOMDocuments again, but this time we're trying something new: First, we append an element to a document, then we try to append that same element to a different document. This is performed in the crossDocumentAppendTest() method:

 

Everything works just fine until we get to the third appendElementToDocument(DOMElement, DOMDocument) call, which attempts to crossover the append action by appending $elementA to $documentB, even though $elementA is already appended to $documentA. As you can probably guess, this also results in a DOMException being thrown, this time indicating that we are manipulating the wrong document (referring to $documentB, in this case):

 

We solve this issue within the crossDocumentAppendTestSuccess() method:

 

As you may have noticed, the appendElementToDocument(DOMElement, DOMDocument) method returns a DOMNode or null object, which we've specified using the : ?DOMNode syntax at the end of the method declaration. This is a new feature introduced in PHP 7.1+, which makes it easy for us to use the result of that method call for further logic. Thus, in crossDocumentAppendTestSuccess() we assign the appendElementToDocument(DOMElement, DOMDocument) result to a $node variable and check if it exists or was null). In the event that no DOMNode instance was returned, that indicates our original append attempt failed (just as in the previous method), so we can perform one little trick necessary to get this crossover append to work.

The solution is to call the importNode(DOMNode) method on the DOMDocument instance that is to receive the new appendage. This imports the node into the document tree, behind the scenes, and returns the resulting DOMNode instance, which we've assigned to $importedNode. From there, it's just a matter of calling the appendElementToDocument(DOMElement, DOMDocument) method one last time, but now we pass the $importedNode instance to $documentB, instead of the original $elementA version.

Our log shows this works just as expected, and the ultimate result is the same, but without throwing an unnecessary DOMException:

 

Check out the Airbrake-PHP library, designed to quickly and easily integrate into any PHP project, giving you and your team access to real-time error monitoring and reporting throughout your application's entire life cycle. With automatic, instantaneous error and exception notifications at your fingertips, you'll be constantly aware of your application's health, including any issues that may arise. Best of all, with Airbrake's robust web dashboard cataloging every error that occurs, you and your team can immediately dive into the exact details of what went wrong, making it easy to quickly recognize and resolve problems.

Written By: Frances Banks