[PHP] DOMElement insert custom HTML using Symfony DomCrawler

Simon AsikaSimon Asika
2 min read

In JavaScript, if we want to insert a custom HTML to an element, there is a convenience way that we can set it into the innerHTML.

const el = document.querySelector('.foo');

el.innerHTML = `<div>FOO</div>`;

But in PHP, although there has a DOM Document implementation, it is only supports HTML4 and not up-to-date with W3C standards for a long time, the innerHTML still not work on PHP DOMDocument extension, so we must use another way to insert custom HTML.

Using Symfony DomCrawler for HTML5

The Symfony DomCrawler Component is a useful package to help us parsing and filtering DOM, and there is a masterminds/html5 package can work with DomCrawler to allow it correctly handle HTML5.

We can install both of them:

composer require symfony/dom-crawler masterminds/html5 symfony/css-selector

Parse HTML

Now we can parse a HTML string to DOMDocument:

use Symfony\Component\DomCrawler\Crawler;

// Some HTML5 string, maybe is an article content, or get by HTTPClient
$html = '...';

// Parse it, you must add `useHtml5Parser: true` 
// to use masterminds/html5 as the parser
$crawler = new Crawler($html, useHtml5Parser: true);

// Filter what you want by CSS selector
$cards = $crawler->filter('.card');

foreach ($cards as $card) {
    // @var $card DOMElement
}

Insert or Replace HTML

Now if you want to insert a simple HTML, you can just use createElement()

$body = $crawler->ownerDocument->createElement('div');
$body->setAttribute('class', 'card-body');
$body->setAttribute('style', '...');

// Append child
$card->appendChild($body);

// Or insertBefore
$card->firstChild->insertBefore($body);

// Or replace
$card->firstChild->replaceWith($body);

Insert from Another DOM

And if you want to insert a HTML string, you must parse it into DOMElement first, here we use Masterminds\HTML5 to parse it:

use Masterminds\HTML5;

$childHTML = '<div class="card-body">...</div>';

$html5 = new HTML5();
$dom = $html5->loadHTML($childHTML);

// Get the node
$node = $dom->documentElement->firstElementChild;

If you directly insert this node:

$card->appendChild($node);

you will receive an error message:

Fatal error: Uncaught exception 'DOMException' with message 'Wrong Document Error'

That because in the DOM standards, every DOM nodes and elements are belongs to only 1 DOMDocument, if you want to insert a DOM node into another Document, you must import them first.

// Now we get the ownerDocument of $card and import nodes
// Remember add the second argument TRUE to deeply import.
$root = $card->ownerDocument->importNode($dom->documentElement, true);

The returned $root is a clone of original DOM node, we can insert it to new DOM:

$card->appendChild($root->firstElementChild);

Save back to HTML

In the current Symfony Crawler version, save as HTML can be very convenience, just use:

$crawler->html();

Note, if you get the child crawler instance, it may print partial of the HTML:

$cards->html();
0
Subscribe to my newsletter

Read articles from Simon Asika directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Simon Asika
Simon Asika