It appears that the PHP team is not willing to change this behavior (source), so we have to find a workaround instead.
One way is to simply do the encoding yourself in the PHP code, as such:
$header = $dom->createElement("h2", "Lorem & Ipsum");
However, this isn't always convenient, as the text printed may be inside of a variable or contain other special characters besides &. So, you can use the htmlentities function.
$text = "Lorem & Ipsum";
$header = $dom->createElement("h2", htmlentities($text));
If this still is not an ideal solution, another workaround is to use the textContent property instead of the second argument in createElement.
In the code below, I've implemented this in a DOMDocument subclass, so you just have to use the BetterDOM subclass instead to fix this strange bug.
class BetterDOM extends DOMDocument {
    public function createElement($tag, $text = null) {
        $base = parent::createElement($tag);
        $base->textContent = $text;
        return $base;
    }
}
// Correctly prints "<h2>Lorem & Ipsum</h2>" with no errors
$dom = new BetterDOM();
$header = $dom->createElement("h2", "Lorem & Ipsum");
$dom->appendChild($header);
print($dom->saveHTML());