windwalker/dom
Windwalker Dom is a PHP package for parsing, querying, and manipulating HTML and XML documents with an intuitive API. It offers node traversal, CSS-style selectors, and safe document building, making it handy for scraping, templating, and content transformation in Laravel or any Composer-based proje...
Install via Composer (the package targets Windwalker 4, so PHP 8.1+ recommended):
composer require windwalker/dom ^4.0
Look here first, in order:
src/ — especially HtmlElement, DomElement, HtmlElements, and src/Builder/HtmlBuilder.php. These are the classes you will actually touch.tests/ — the test suite is effectively the API reference for edge cases (void tags, boolean attributes, escaping).windwalker-io/framework).Minimal first example — build a fragment without string concatenation:
use Windwalker\DOM\Builder\HtmlBuilder;
$html = HtmlBuilder::create('div', ['class' => 'alert alert-danger'], 'Something went wrong');
echo $html; // <div class="alert alert-danger">Something went wrong</div>
Or with the object API:
use Windwalker\DOM\HtmlElement;
$el = new HtmlElement('a', ['href' => '/users/1'], 'Profile');
echo $el->toString(); // or (string) $el
First real use case: replacing hand-built HTML strings in a controller, mail builder, or CLI report generator where you need to assemble nested markup from data. Start with a small helper that returns an HtmlElement and echo/concatenate it inside your existing templates.
Verify exact class names against
src/for your installed version — Windwalker has bumped namespaces and class locations across major versions.
Fragment helpers instead of inline concatenation. Wrap repeated markup in small factory functions that return HtmlElement objects. This keeps templates readable and centralizes attribute handling:
function alert(string $type, string $message): HtmlElement
{
return new HtmlElement('div', ['class' => "alert alert-$type", 'role' => 'alert'], $message);
}
echo alert('danger', $exception->getMessage());
Compose nested trees, render once. Use appendChild()/child content to build depth, then render at the boundary (controller output, mail body). This is the "virtual DOM" payoff: structure is data, string is a final step.
$list = new HtmlElement('ul', ['class' => 'items']);
foreach ($items as $item) {
$list->appendChild(new HtmlElement('li', ['data-id' => $item->id], $item->name));
}
echo $list; // <ul class="items"><li data-id="1">…</li>…</ul>
Builder primitives for common widgets. The HtmlBuilder class historically exposes helpers like attributes(), input(), option(), options(), checkbox(), textarea(). Reach for these instead of rebuilding attribute serialization by hand — this is where escaping and boolean-attribute rules are already correct.
Collections for tables/lists. HtmlElements (an array-like container) is useful when you want to pass a group of elements around before rendering, or map/reduce over children.
Integration strategy.
{!! !!} slot (only for trusted content — see Gotchas).Html service/interface around the builder and bind it in the container. With only 9 stars and 0 dependents, you want one seam to swap the library later.Testing. Assert against rendered strings with assertSame(); the normalized attribute ordering (see below) makes output deterministic, which makes snapshot-style tests viable.
Config. There is essentially no config file — behavior is controlled per-call (XHTML flag on create(), attribute arrays, content-type escaping). Treat constructor/builder arguments as your configuration surface and centralize them in helpers.
Illuminate\View-style behavior.Windwalker\DOM to Windwalker\Dom (and class renames like DomElement/HtmlElement) between majors. Pin ^4.0, check composer.lock, and read the installed src/ rather than copying snippets from older blog posts.HtmlElement/raw strings, they are not re-escaped. Never pass user input into a "raw" content path. Decide per helper whether content is trusted.&amp;. Let the element handle text content; only escape at the boundary.input, img, br, hr, meta, link require the void-element path (HtmlBuilder::input() or the builder's void handling). Writing <input></input> by hand or via a generic create() can produce invalid HTML. Use the dedicated helpers or pass the XHTML flag consciously.['disabled' => true] may render as disabled="disabled" or disabled="1" depending on the API path; use the HtmlBuilder::boolean()/attribute helper rather than raw arrays if exact output matters.protected methods on DomElement) in case the mirror stops syncing.var_dump((string) $el) at each level, or json_encode a simplified tree, rather than stepping through builder internals. When output differs from expectation, check whether the child was appended as a string vs an element first.HtmlElement or add your own static builder helpers for domain-specific tags; that keeps call sites short and gives you one place to change if you migrate off the package.How can I help you explore Laravel packages today?