21torr/html-builder
Fluent HTML builder for Laravel/PHP. Programmatically compose tags, attributes, and nested elements with a clean API to generate consistent markup without mixing large HTML strings into your views or controllers.
Installation:
composer require 21torr/html-builder
No additional configuration is required—just autoload the package.
First Use Case: Generate a simple HTML element (e.g., a button) with attributes and nested content:
use HtmlBuilder\Html;
$button = Html::tag('button')
->withAttributes(['class' => 'btn btn-primary'])
->withText('Click Me')
->render();
echo $button;
Output:
<button class="btn btn-primary">Click Me</button>
Key Classes to Explore:
Html::tag(): Start building an element.->withAttributes(): Add attributes (e.g., class, id, data-*).->withText()/->withHtml(): Add content (sanitized or raw).->render(): Output the final HTML string.->getParent(): Retrieve the parent element of a nested child.Dynamic Element Generation: Use loops or conditionals to build reusable components:
$items = ['Home', 'About', 'Contact'];
$nav = Html::tag('nav');
foreach ($items as $item) {
$nav->addChild(
Html::tag('a')
->withAttributes(['href' => route('page', $item)])
->withText($item)
);
}
Nested Structures with Parent Access:
Build complex HTML hierarchies and traverse upward using getParent():
$card = Html::tag('div', ['class' => 'card']);
$body = Html::tag('div', ['class' => 'card-body']);
$card->addChild($body);
$body->addChild(
Html::tag('h5')->withText('Title')
);
// Access parent from child
$parentClass = $body->getParent()->getAttributes()['class'];
// $parentClass = 'card'
Integration with Laravel Blade:
Create helper functions in app/Helpers/html.php:
if (!function_exists('btn')) {
function btn(string $text, array $attrs = []): string {
return Html::tag('button')
->withAttributes($attrs)
->withText($text)
->render();
}
}
Usage in Blade:
{!! btn('Submit', ['class' => 'btn-danger']) !!}
Form Handling: Generate forms with CSRF protection and validation:
$form = Html::tag('form', [
'action' => route('submit'),
'method' => 'POST',
])->withHtml(csrf_field());
$form->addChild(
Html::tag('input')
->withAttributes([
'type' => 'text',
'name' => 'email',
'value' => old('email'),
])
);
->withText() for auto-escaping; ->withHtml() for raw output (be cautious with user input).Form/Html helpers for consistency:
use HtmlBuilder\Html;
use Illuminate\Support\Facades\Html as LaravelHtml;
$link = Html::tag('a')
->withAttributes([
'href' => LaravelHtml::route('profile'),
])
->withText('Profile');
Html instances in unit tests:
$htmlMock = Mockery::mock('overload:\HtmlBuilder\Html');
$htmlMock->shouldReceive('tag')->andReturnSelf();
$htmlMock->shouldReceive('render')->andReturn('<div>Test</div>');
$htmlMock->shouldReceive('getParent')->andReturnNull();
Double Escaping:
Chaining withText() and withHtml() may lead to double-escaping. Prefer one or the other per element:
// ❌ Avoid:
Html::tag('div')->withText('<strong>Bold</strong>')->withHtml('...');
// ✅ Do:
Html::tag('div')->withHtml('<strong>Bold</strong>');
Attribute Injection: User-provided attributes (e.g., from requests) can introduce XSS. Sanitize or whitelist:
$safeAttrs = collect($request->input('attrs', []))
->filter(fn($v) => !str_starts_with($v, 'on'))
->toArray();
Memory Leaks:
Avoid storing large Html instances in session/cache. Render to string immediately:
// ❌ Bad:
$session->put('html', $complexHtmlInstance);
// ✅ Good:
$session->put('html', $complexHtmlInstance->render());
Parent Access Edge Cases:
Calling getParent() on a root element (not added as a child) returns null. Always check:
if ($child->getParent()) {
$parentAttrs = $child->getParent()->getAttributes();
}
getAttributes() to debug attribute bags:
$element = Html::tag('div')->withAttributes(['data-id' => 1]);
dd($element->getAttributes()); // ['data-id' => '1']
render() at each step to catch errors:
$element = Html::tag('div');
$element->withText('Test')->render(); // Fail fast if invalid.
$child = Html::tag('span');
$parent = Html::tag('div')->addChild($child);
$current = $child;
while ($current->getParent()) {
$current = $current->getParent();
dd($current->getAttributes()); // Inspect each parent
}
Custom Tags:
Extend the builder for domain-specific tags (e.g., Html::tag('alert')):
Html::macro('alert', function($type, $message) {
return Html::tag('div', ['class' => "alert alert-$type"])
->withText($message);
});
Usage:
Html::alert('danger', 'Error!')->render();
Attribute Modifiers: Add methods to modify attributes dynamically:
Html::macro('addClass', function($class) {
$this->withAttributes(['class' => $this->getAttribute('class', '') . ' ' . $class]);
return $this;
});
Usage:
Html::tag('div')->addClass('active')->render();
Event Listeners: Hook into the rendering process for analytics or logging:
Html::macro('onRender', function($callback) {
$originalRender = $this->render;
return function() use ($originalRender, $callback) {
$html = $originalRender();
return $callback($html, $this);
};
});
Usage:
$element->onRender(function($html, $element) {
// Log element structure.
return $html;
});
Parent-Aware Macros:
Leverage getParent() to create macros that interact with the DOM hierarchy:
Html::macro('getGrandParent', function() {
return $this->getParent()?->getParent();
});
Usage:
$grandParentClass = $child->getGrandParent()?->getAttributes()['class'] ?? null;
Html::macro('myApp\alert', function(...) { ... });
How can I help you explore Laravel packages today?