anahkiasen/html-object
HtmlObject brings object-oriented HTML generation to PHP and Laravel. Build tags, lists, tables, and forms through a fluent, chainable API instead of messy string concatenation. Each element is a dedicated class you can extend, keeping markup clean, reusable, and easy to maintain.
Install with Composer and confirm it loads on your PHP version before writing code against it:
composer require anahkiasen/html-object
The autoloaded namespace is HtmlObject\. Read these first, in order:
src/HtmlObject/Element.php — the general-purpose <tag> builder you will use 90% of the time.src/HtmlObject/Tag.php — the base class holding render() / __toString().src/HtmlObject/Traits/ — HasAttributes, HasChildren, and HasContent define the magic-property API.src/HtmlObject/{Link,Image,Input}.php — thin typed subclasses with pre-wired attributes.tests/ — the exact signatures matter (they are not uniform across classes).First use case — replace string-concatenated markup in a controller, mailer, or view helper:
use HtmlObject\Element;
$card = new Element('div', null, ['class' => 'card']);
$card->nest('h2', 'Welcome');
$card->nest('p', 'Thanks for signing up.');
echo $card;
// <div class="card"><h2>Welcome</h2><p>Thanks for signing up.</p></div>
Sanity-check with (string) $card in a REPL. The object renders to a string, so it drops into anything that accepts a string — Blade, a mailer body, a JSON response field.
Set attributes three ways, pick the right one. Magic properties are the ergonomic default, but they cannot express names with hyphens or colons, so fall back to setAttribute() or the constructor array:
$el->class = 'btn'; // magic __set
$el->setAttribute('data-id', 42); // dashed/namespaced attributes
$el = new Element('a', 'Go', ['href' => '/x', 'class' => 'btn']); // bulk
Build children with nest(), which returns the child. That return value is what makes fluent tree building readable — chain off the child, not the parent:
$ul = new Element('ul', null, ['class' => 'menu']);
foreach ($items as $item) {
$ul->nest('li', $item)->nest('span', ' →');
}
Prefer the typed helpers over new Element() when the tag has required attributes. They save you from remembering attribute order and make intent obvious in reviews:
use HtmlObject\Link;
use HtmlObject\Image;
use HtmlObject\Input;
echo Link::create('/login', 'Sign in', ['class' => 'btn']);
echo Image::create('/logo.png', 'Acme', ['width' => 120]);
echo Input::create('email', 'email', null, ['required' => true]);
Wrap recurring markup in a component class. This is where the package earns its keep — you get one place to change a card/alert/badge and every call site updates:
class Alert extends \HtmlObject\Element
{
public function __construct(string $message, string $type = 'info')
{
parent::__construct('div', $message, ['class' => "alert alert-{$type}"]);
}
}
echo new Alert('Saved.', 'success');
Override render() only if you need custom void-element or self-closing behavior; otherwise let the base class do it.
Splice dynamic layout trees. Build the shell once, then append/prepend/remove named nodes:
$layout = new Element('div', null, ['class' => 'layout']);
$layout->nest('header');
$main = $layout->nest('main');
$main->append($widget); // append an existing Element
$main->prepend($banner);
$layout->removeChild(0); // drop header
Assert on getChildren() / hasChildren() in tests rather than squinting at markup.
Render at the boundary, cache the string. Element objects are mutable; if you stash the object and mutate it later, every consumer sees the new state. Pass rendered strings between layers and keep the tree local to one method.
Blade integration is just echo. Because __toString() exists, {!! $card !!} works. If you want a terser syntax, write a helper:
function el(...$args): \HtmlObject\Element { return new \HtmlObject\Element(...$args); }
<script>alert(1)</script> payload, then escape user input (htmlspecialchars($s, ENT_QUOTES, 'UTF-8'), or e() in Laravel) before handing it to the builder.data-* or aria-*. $el->dataFoo = 1 is fine; $el->data-foo will not parse. Use setAttribute('data-foo', 1) or the constructor array.Image, Input, br, hr, meta have no children. nest() there produces invalid markup or silently drops it.nest() returns the child, not the parent. The most common bug is chaining a second call expecting the parent. Keep explicit references when a chain gets longer than two calls.Link::create($url, $content, $attributes), Image::create($src, $alt, $attributes), Input::create($type, $name, $value, $attributes) — don't guess, read the source; they are not normalized.<div></div> vs. a self-closed form) depending on the version. Pin your expected output with (string) assertions in tests.(string) $el, getAttributes(), getChildren(). If the markup looks wrong, dump the tree before blaming the renderer.<li>s built in a loop, plain string concatenation is faster — use this package where you actually manipulate the tree, not as a rendering engine for bulk output.spatie/laravel-html, Blade partials, or raw DomDocument (for parsing/manipulating existing HTML). This package fits best when you want small, typed, nestable element builders and are comfortable maintaining a dormant dependency.How can I help you explore Laravel packages today?