mmucklo/krumo
Krumo is a drop-in replacement for print_r() and var_dump() that renders PHP arrays and objects as collapsible, color-coded DHTML trees. This Composer-ready fork makes debugging nested data easy, showing types, values, and structure at a glance instead of dumping raw text.
Krumo is a standalone PHP class — there is no service provider, no facade, no config file to publish. It renders any PHP value as a collapsible HTML tree (types, lengths, object class/properties, array keys) with per-node expand/collapse.
composer require mmucklo/krumo
<link>/<script> tags pointing at a URL you configure, so copy or symlink the package's CSS/JS into the public directory:cp -r vendor/mmucklo/krumo/resources public/vendor/krumo
boot(), or a local-only middleware):krumo::setDir('/vendor/krumo/');
Route::get('/debug', function () {
krumo::dump(User::with('posts')->first(), 'user');
});
Where to look first: the main class file (class.krumo.php or similar) for the static API surface and config keys, and the resources/ folder for the assets. The fork's README is the authoritative list of what was added over the long-dead upstream — skim it before assuming upstream behaviour.
First real use case: a throwaway local route (or a if (app()->environment('local')) block in an existing controller) to inspect an Eloquent model with relations, a request payload, or a third-party API response, without spinning up Xdebug.
Gate everything behind the environment. Krumo writes raw HTML straight to the output stream; there is no "off switch" by default.
// AppServiceProvider::boot()
if ($this->app->environment('local')) {
krumo::setDir('/vendor/krumo/');
}
Optionally add a local-only middleware that calls krumo::disable() when the request expects JSON, so a stray dump never corrupts an API response.
Define a project-local kd() shortcut if you want dump-and-die semantics. Keep the name distinct from Laravel's dd() so you can mix both:
function kd(...$vars) {
foreach ($vars as $v) { krumo::dump($v); }
exit;
}
Capture output instead of printing it. Wrapping the call in an output buffer is the reliable way to get Krumo's HTML as a string — useful for writing to a log file, returning it in an AJAX debug endpoint, or attaching it to an exception report:
ob_start();
krumo::dump($payload, 'payload');
$html = ob_get_clean();
Log::debug($html); // or return response($html)
This is also the pattern for debugging AJAX/JSON flows: add a ?debug=1 branch that returns the captured HTML as a text/html response rather than polluting the JSON body.
Label every dump. krumo::dump($var, 'name') puts a title above the tree; when you have five dumps on one page, unlabelled trees are useless.
Dump the shallow shape, not the object. For Eloquent, $model->toArray() or $model->getAttributes() gives a far more readable tree than the model instance, whose protected properties and loaded-relation graph are noisy. Use the instance only when you specifically need to see relation loading state.
Configure per use case, not globally. krumo::setConfig([...]) accepts an array and its effect is static/per-request, so it's safe to change it mid-request — e.g. tighter sorting or display settings right before a specific dump, then reset. Treat the config as write-once-per-script unless you actually need variation.
Asset route alternative. If you don't want files in public/, register a tiny route that serves the CSS/JS from the vendor path and point setDir() at it. This keeps deploys clean but adds a route; either is fine.
Debug endpoint pattern for tinker-shy teammates. A single /__debug route guarded by local + an allow-list of query params (model class, ID, relation) gets 90% of the value of Krumo without anyone needing a shell. Krumo is the rendering layer; the route is the ergonomics.
krumo is a lowercase class name and it defines a global krumo() function. It's classic PHP, autoloaded via classmap, not PSR-4. Don't try to namespace it, and don't define your own krumo() helper.dump() wrapper. Laravel already ships Symfony's VarDumper dump(); shadowing or redeclaring it produces confusing "cannot redeclare" fatals depending on load order. Use krumo::dump() explicitly or your own kd().dd() equivalent. Krumo prints and returns; it does not halt. Forgetting exit after a dump means the rest of the page renders and you may be reading a half-built response.setDir() is unset or wrong, you get an unstyled nested list that never expands — the data is all there, just collapsed and ugly. Check the browser network tab for 404s on krumo.css/krumo.js first, before suspecting the data.disable().$GLOBALS, framework container internals — dump the toArray()/getAttributes() slice instead of the live object graph. If you must dump the object, do it on a small fixture, not on app().disable()/enable() and setDir()/setConfig() persist for the whole request (and across requests in a long-running worker like Octane). If you disable Krumo for a JSON route, remember the effect can leak within that worker — reset it or rely on per-request bootstrap in boot().<textarea>, an attribute, or between tags in a table cell will produce invalid markup.mmucklo/krumo and ignoring the original krumo package is the correct choice. If something breaks on a new PHP version, the fork's issue tracker — not upstream — is where the fix lands.require-dev and out of production builds sidesteps the question entirely.require-dev and to a pre-commit grep. grep -rn "krumo::dump\|krumo(" app/ routes/ in a git hook is a cheap way to stop debug dumps reaching review.How can I help you explore Laravel packages today?