vardumper/extended-htmldocument
ExtendedHtmlDocument is a PHP library that extends DOMDocument with convenient helpers for loading, querying, and manipulating HTML. It reduces boilerplate for common DOM tasks, making it easier to find elements, traverse markup, and apply changes in scraping or transformation workflows.
vardumper/extended-htmldocument wraps PHP 8.4's native Dom\HTMLDocument (the new HTML5 spec-compliant parser) and adds the conveniences you actually want in application code: CSS selector queries (querySelector / querySelectorAll), XPath, and a cleaner element API. It is framework-agnostic but drops neatly into a Laravel app for scraping, sanitizing, and asserting on HTML.
Minimal steps:
php -v). The package relies on Dom\HTMLDocument, so it will not install/run on lower PHP versions.composer require vardumper/extended-htmldocumentuse Vardumper\ExtendedHtmlDocument\ExtendedHtmlDocument;
$doc = ExtendedHtmlDocument::createFromString($html);
$title = $doc->querySelector('h1.title')?->textContent;
$links = $doc->querySelectorAll('a[rel="nofollow"]');
ExtendedHtmlDocument class itself — it's a thin layer over Dom\HTMLDocument, so the underlying DOM API is your reference for anything not wrapped.Http::get() and extracts a handful of fields with CSS selectors instead of brittle regex or regex-adjacent str_* calls.Selector-based extraction in a service. Keep DOM work out of controllers; wrap it in a small service or a HtmlExtractor class that accepts a string and returns a DTO. This is testable and keeps the parser detail swappable.
final class ProductPageParser
{
public function parse(string $html): ProductData
{
$doc = ExtendedHtmlDocument::createFromString($html);
return new ProductData(
name: $doc->querySelector('h1[itemprop="name"]')?->textContent,
price: $doc->querySelector('.price')?->textContent,
images: array_map(
fn ($el) => $el->getAttribute('src'),
iterator_to_array($doc->querySelectorAll('.gallery img'))
),
);
}
}
Pairing with the HTTP client. Http::get($url)->body() feeds directly into createFromString(). Use retry(), timeouts, and ->throw() on the client so the parser only ever sees real HTML.
Parsing files and fixtures. For uploaded/generated HTML, use createFromFile($path) where available, or file_get_contents() + createFromString(). In feature tests, store representative HTML as fixtures and assert on extracted values rather than on raw markup strings.
Rendering checks in tests. For Blade output assertions, parse the rendered string and query it — assertSee only does substring matching, whereas selector queries prove structure:
$doc = ExtendedHtmlDocument::createFromString($this->get('/')->getContent());
$this->assertNotNull($doc->querySelector('nav a[aria-current="page"]'));
Sanitization/rewriting workflows. Because you get a real DOM tree, you can walk it, mutate nodes, and serialize back out — useful for stripping elements or rewriting links before storing content.
XPath for the cases CSS can't express. Fall back to XPath when you need predicates on text content, sibling axes, or namespaced attributes; CSS selectors cover the common 90%.
platform in composer.json and your Docker base image before adopting.composer.json and read changelogs on every minor upgrade.0.3.4...0.3.5 on GitHub or run your test suite against the new tag; treat patch bumps as low-risk but not zero-risk on a 0.x package.DOMDocument. Dom\HTMLDocument follows HTML5 error-recovery rules, so malformed markup parses differently than DOMDocument/libxml_use_internal_errors. Don't assume identical node trees if migrating existing code.querySelector returns null. Always null-safe-chain (?->textContent) — a missing selector is silent, not an exception. For required fields, validate explicitly and throw a domain exception so scraping drift fails loudly rather than silently storing nulls.querySelectorAll ergonomics. It's not a foreach-friendly live list in every version — wrap with iterator_to_array() or foreach carefully depending on the return type, and avoid relying on live-updating node lists after mutation.:has() or newer CSS4 selectors may be unsupported. Fall back to XPath (queryXPath) when a selector silently returns nothing.mb_convert_encoding if needed). The parser honors declared charsets, but inconsistent encoding is a classic source of mangled textContent.eval or execute anything from parsed nodes.textOf(string $selector) convenience methods) rather than forking the package.How can I help you explore Laravel packages today?