schleuse/dindent
Dindent is a regex-based HTML indenter/beautifier for development and testing. It formats template-generated markup with readable indentation without sanitizing, fixing, or rebuilding the document like DOM/Tidy—useful for debugging messy HTML output.
Installation (PHP 8.2+ required):
composer require schleuse/dindent:^3.0
No additional configuration is required—just autoload via Composer.
First Use Case (New: One-line mode):
use Schleuse\Dindent\Dindent;
$dirtyHtml = '<div><span>Hello</span></div><p>World</p>';
// Standard indentation
$indented = Dindent::indent($dirtyHtml);
// Outputs:
// <div>
// <span>
// Hello
// </span>
// </div>
// <p>
// World
// </p>
// New: One-line mode (compact output)
$oneLine = Dindent::indent($dirtyHtml, ['oneLine' => true]);
// Outputs:
// <div><span>Hello</span></div><p>World</p>
Where to Look First:
v3.0.0 branch for new features).Dindent::indent() method docs (now supports oneLine, skipScripts, and handles <style> tags).<pre>, multi-line comments).Debugging HTML Responses (New: One-line mode for APIs):
// In a controller or middleware (compact for APIs)
$responseHtml = $response->getContent();
$indentedHtml = Dindent::indent($responseHtml, ['oneLine' => $request->wantsJson()]);
Log::debug('Indented HTML:', ['html' => $indentedHtml]);
Testing Views (New: Handle <style> and <pre> tags):
// In a PHPUnit test (assert style tags are preserved)
$viewOutput = $this->get('/some-route')->getContent();
$indented = Dindent::indent($viewOutput);
$this->assertStringContainsString('<style>', $indented);
$this->assertStringContainsString(' <pre>', $indented); // Indented but not broken
Pre-commit Hooks (New: Optimized for speed):
// In app/Console/Kernel.php (faster with one-line mode)
Artisan::command('dindent:fix', function () {
$files = $this->argument('paths');
foreach ($files as $file) {
$html = file_get_contents($file);
file_put_contents($file, Dindent::indent($html, ['oneLine' => true]));
}
});
Blade Directives (New: Support for <style> and one-line mode):
Blade::directive('indent', function ($expression) {
return "<?php echo \\Schleuse\\Dindent\\Dindent::indent({$expression}, ['oneLine' => false]); ?>";
});
Usage in Blade:
@indent($unformattedHtml) <!-- Standard indentation -->
@php echo \Schleuse\Dindent\Dindent::indent($html, ['oneLine' => true]); @endphp <!-- Compact -->
API Responses (New: Dynamic indentation based on request):
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->headers->get('Content-Type') === 'text/html') {
$options = $request->wantsJson() ? ['oneLine' => true] : [];
$response->setContent(Dindent::indent($response->getContent(), $options));
}
return $response;
}
Artisan Commands (New: Extract <pre> and <textarea>):
Artisan::command('dindent:fix', function () {
$files = $this->argument('paths');
foreach ($files as $file) {
$html = file_get_contents($file);
$indented = Dindent::indent($html, [
'oneLine' => false,
'extractPre' => true, // Preserve <pre> content
]);
file_put_contents($file, $indented);
}
});
PHP 8.2 Requirement (⚠️ Breaking):
php.ini or use a PHP 8.2+ runtime (e.g., Laravel Valet, Docker, or php@8.2).Self-Closing Tags in One-Line Mode:
Dindent now preserves self-closing tags (e.g., <img/>) even in compact mode:
$html = '<div><img src="x" /><span>Text</span></div>';
Dindent::indent($html, ['oneLine' => true]);
// Outputs: <div><img src="x" /><span>Text</span></div>
<style> and <script> Handling:
<style> tags are now properly indented (previously ignored).skipScripts to opt-out:
Dindent::indent($html, ['skipScripts' => true]);
<pre> and <textarea> Extraction:
<pre> or <textarea> is preserved as-is (not indented).<pre> content, use extractPre: false:
Dindent::indent($html, ['extractPre' => false]);
Performance in One-Line Mode:
Edge Cases (New: Multi-line comments and <pre>):
Test with:
$html = <<<HTML
<div>
<!--
Multi-line
comment
-->
<pre> Preserve this indentation!
</pre>
</div>
HTML;
$indented = Dindent::indent($html);
// Multi-line comments and <pre> content remain intact.
Logging Differences:
Compare oneLine vs. standard output:
$original = '<div>Test</div>';
$standard = Dindent::indent($original);
$compact = Dindent::indent($original, ['oneLine' => true]);
$this->assertNotEquals($standard, $compact, 'Modes produce different outputs!');
Custom Indentation with One-Line Mode: Override the class to combine custom indentation with new features:
class CustomDindent extends Dindent {
public static function indent($html, array $options = []) {
$options['indent'] = ' '; // 4 spaces
$options['oneLine'] = false;
return parent::indent($html, $options);
}
}
Extension Points (New: extractPre and skipScripts):
Use flags to control extraction behavior:
Dindent::indent($html, [
'extractPre' => true, // Default: preserve <pre> content
'skipScripts' => false, // Default: indent <script> and <style>
]);
Visual Diffs for One-Line Mode: Compare compact vs. standard output:
echo "Standard:"; echo "$standardHtml" | diff -u - <(echo "$compactHtml")
CI Integration (New: Test both modes):
public function test_indentation_modes()
{
$html = '<div><p>Test</p></div>';
$standard = Dindent::indent($html);
$compact = Dindent::indent($html, ['oneLine' => true]);
$this->assertStringContainsString("\n <p>", $standard);
$this->assertStringNotContainsString("\n", $compact);
}
Partial Indentation with <pre>:
Indent everything except <pre> blocks:
preg_match_all('/<pre>(.*?)<\/pre>/s', $html, $preBlocks);
$indented = Dindent::indent($html, ['extractPre' => true]);
foreach ($preBlocks[1] as $i => $block) {
$indented =
How can I help you explore Laravel packages today?