machy8/xhtml-formatter
A lightweight PHP/Laravel package for formatting and pretty-printing HTML/XHTML output. Clean up markup, normalize indentation, and produce well-formed, readable documents—ideal for Blade views, emails, and generated content.
Install via Composer and confirm it resolves on your PHP version before writing any code:
composer require machy8/xhtml-formatter
The package exposes a small formatter API. The first file to open in vendor/machy8/xhtml-formatter is the Formatter class (and the README) — it is the entire public surface, so there is little to discover beyond its constructor options and the format() method. A typical first call looks like this:
use Machy8\XHTMLFormatter\Formatter;
$formatter = new Formatter();
$pretty = $formatter->format('<div><p>Hello</p><p>World</p></div>');
Your first real use case is almost always "make compiled Blade output or a stored HTML snippet readable" — either for debugging rendered views, for diffing HTML in tests, or for pretty-printing stored email/newsletter templates. Start by running it against one of your actual templates and eyeballing the result before trusting it on a whitespace-sensitive page.
Because the package is lightweight and unmaintained since 2017, treat the README/example folder as the source of truth, pin the version in composer.json, and verify it loads under your PHP version immediately. If it fails on PHP 8+, stop here and evaluate a maintained alternative (e.g. tidy) before investing further.
Wrap it behind a service, never call it inline. Bind a thin service that owns the Formatter instance so you can swap it later:
// app/Services/HtmlFormatter.php
namespace App\Services;
use Machy8\XHTMLFormatter\Formatter;
class HtmlFormatter
{
public function __construct(private Formatter $formatter) {}
public function pretty(string $html): string
{
return $this->formatter->format($html);
}
}
Register it as a singleton in a service provider and inject the interface where needed.
Debugging rendered views. A Blade directive or a local-only helper is the highest-value day-to-day use:
Blade::directive('pretty', fn ($expr) => "<?php echo app(\\App\\Services\\HtmlFormatter::class)->pretty($expr); ?>");
Then in a local view: @pretty($someRenderedHtml).
Formatting stored fragments / email templates. Run it in an artisan command that reads HTML from the DB (or from resources/views/mail), formats, and writes back — keep this out of the request path.
// app/Console/Commands/FormatTemplates.php
foreach (Template::cursor() as $tpl) {
$tpl->update(['body' => $formatter->pretty($tpl->body)]);
}
Test assertions. If you assert against rendered HTML in feature tests, formatting both the expected and actual output with the same formatter makes assertions readable and diff-friendly. Add a helper to your base TestCase.
Fragments vs. full documents. Feed it complete documents (with <!DOCTYPE>) when you need correct structure, and discrete fragments when formatting partials. Keep the two paths in separate methods on your service so you don't accidentally reflow a full page through a fragment-oriented code path.
Pipeline placement. Use it in build/CI steps (post-processing compiled assets or generated docs) rather than in per-request middleware. If you must format responses, restrict it to local/staging environments and only for text/html responses.
format('<div><span>x</span></div>')) on your target PHP version before adopting. If it errors on modern PHP, do not patch vendor/ — fork or pick another tool.<pre>, <textarea>, <code>, and inline elements (<a>, <span>, <strong>) survive a round-trip unchanged. Reformatters that introduce newlines around inline tags can change visual rendering. Always diff formatted vs. original in a browser for your key templates.DOMDocument/tidy first if you need structural guarantees.<script>/<style> blocks are common failure points. Confirm UTF-8 content and that scripts/styles are passed through untouched; if they are reformatted, pre-strip or skip those regions.config/html_formatter.php and map it into the constructor so settings live with the rest of your app config.composer.json note (or a test) documenting the pinned version and the PHP versions you have verified, so a future composer update doesn't silently swap in an untested state.How can I help you explore Laravel packages today?