zenstruck/dom
Zenstruck DOM is a tiny PHP/Laravel-friendly library for working with HTML/XML DOM. It offers a simple, fluent API to query, traverse, and manipulate nodes, making scraping, testing, and HTML transformations easier than using raw DOMDocument/DOMXPath.
Start by installing via Composer: composer require zenstruck/dom. The primary entry point is the Zenstruck\Dom\Dom class — instantiate it with HTML using Dom::fromHtml($markup) or from a response body. Your first step is typically parsing and extracting data:
$dom = Dom::fromHtml($html);
$title = $dom->find('h1')->text();
For Laravel, integrate it directly into feature tests: fetch a page with ->get(), pass ->response()->original’s content, and validate structure without browser automation.
$dom = Dom::fromHtml($this->get('/products')->content());
$dom->assert()->count('.product-card', 10);
$dom->find('.hero-title')->assert()->contains('New Collection');
findAll() → map() for structured data:
$items = $dom->findAll('.item')->map(fn ($node) => [
'name' => $node->find('.name')->text(),
'price' => $node->find('.price')->text(),
]);
class Dom extends \Zenstruck\Dom\Dom {
public function findPrice(): ?string { return $this->find('.price')->text(); }
}
Http for scraping:
$html = Http::get($url)->body();
$dom = Dom::fromHtml($html)->assert()->success();
Dom::fromHtml($html, true) if the second arg enables HTML5 parsing, or preprocess with tidy.findAll() returns an iterable collection — assertions like ->assert()->text(...) operate on each node, not collectively. Use first() or last() to assert on a single element.->text(true) (if trim-aware) or wrap extraction: trim($node->text()).Dom::fromXml() requires explicit namespace registration in XPath-like selectors — verify syntax in tests.Dom::extend() for custom method hooks — invaluable when core functionality lags.->assert()->exists() checks the current selector context — avoid chaining findAll() before assertions unless intentional. Preface with find() for precision.How can I help you explore Laravel packages today?