symfony/var-dumper
Symfony VarDumper replaces var_dump() with a clearer, structured dump() for any PHP variable, with syntax highlighting, depth control, and cli/browser output. Great for debugging arrays and objects.
Laravel already ships symfony/var-dumper as a hard dependency of laravel/framework — it is what powers the dump() and dd() helpers. There is nothing to install or wire up in a normal Laravel app:
composer why symfony/var-dumper # confirm it's there and at what version
composer show symfony/var-dumper
For a non-Laravel PHP project (a package, a script, a legacy app) install it directly and rely on Composer's autoloaded dump() function:
composer require --dev symfony/var-dumper
require 'vendor/autoload.php';
dump($someNestedThing); // prints and continues, returns the value
dd($someNestedThing); // prints and exits
Where to look first when you need to go beyond the basics:
Symfony\Component\VarDumper\VarDumper::setHandler() — the single global entry point; every dump() call funnels through the handler installed here.Dumper/CliDumper.php and Dumper/HtmlDumper.php — the two dumpers you'll actually configure (colors, theme, max string width, dump header).Cloner/VarCloner.php + Cloner/AbstractCloner.php — depth, item caps, and where casters are registered.Caster/ — the built-in casters for DateTime, Exception, PDO, DOM, sockets, etc. This is the best source of examples for writing your own.First use case worth adopting on day one: replace every var_dump()/print_r() in your working branch with dump(). Same call site, dramatically better output — recursion handled (&1 reference markers instead of an infinite blow-up), colors, source file/line header, and no Xdebug nesting limits applied.
Inline inspection without losing the value. dump() returns what you passed it, so it composes:
$user = dump(User::with('roles')->first()); // still a User instance
return dump($response->json()); // dump, then return
dd() returns nothing and terminates with exit code 1 — use it for hard stops only, and prefer dump() anywhere the code needs to keep running.
Framework-aware dumping in Laravel. Laravel's own helpers exist, so use them instead of importing Symfony classes:
$query->dump(); // SQL + bindings, execution continues
$query->dd(); // same, then exit
collect($rows)->dump(); // Collection::dump()
In Blade, use the directives rather than raw PHP, because the HTML dumper output needs to land inside the rendered document:
@dump($user)
@dd($errors->all())
Dump server for "can't touch the response" situations. When the dump would corrupt an API payload, a streamed download, or a redirect, push dumps out-of-band:
php artisan dump-server # listens on tcp://127.0.0.1:9912
# .env — only for local
VAR_DUMPER_FORMAT=server
VAR_DUMPER_SERVER=tcp://127.0.0.1:9912
Dumps now appear in the terminal running the server instead of the HTTP response. This is the single most useful pattern for API work, queued jobs, and php artisan serve setups where browser output is unreliable.
Custom casters for your own value objects. Dumping a DTO gives you private properties with noise. Teach the dumper what matters:
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Caster\ConstStub;
use Symfony\Component\VarDumper\Caster\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\VarDumper;
// AppServiceProvider::boot(), guarded for local only
if ($this->app->environment('local')) {
VarDumper::setHandler(function ($var) {
$cloner = new VarCloner();
$cloner->addCasters([
App\Money::class => function (App\Money $m, array $a, Stub $stub, bool $isNested) {
return [
Caster::PREFIX_VIRTUAL.'amount' => $m->amount,
Caster::PREFIX_VIRTUAL.'currency' => new ConstStub($m->currency, $m->currency),
];
},
]);
(new CliDumper())->dump($cloner->cloneVar($var));
});
}
Caster::PREFIX_VIRTUAL adds a synthetic (non-existent) property; ConstStub renders a friendly string representation; Stub lets you inspect the original object. Casters can also be scoped: VarCloner::addCasters() merges with defaults, setCasters() replaces them.
Keeping large structures readable. The cloner caps collections at 2,500 items by default and will render … past that, plus depth truncation. If you're debugging a genuinely huge payload, raise the caps on your own cloner inside the handler rather than fighting the output:
$cloner->setMaxItems(50000);
Choosing the output format explicitly. The dumper is auto-selected: CLI in terminal contexts, HtmlDumper in web contexts when the Accept header contains html. For a JSON endpoint where you still want a readable dump, force the CLI dumper via your handler (as above) so the output is plain text rather than injected markup.
Pairing with the framework's own debugging surfaces. dump() is not a replacement for Log::info() / ->dd() on structured data in jobs; for anything asynchronous, log or use the dump server. For query analysis, ->dump() on the builder beats dumping the model collection.
dd() exits with code 1. In PHPUnit this looks like a failed test, and in CI it truncates the run. Guard it or swap to dump() while iterating.dump() inside an HTTP response can break the request. It writes directly to output, so it can fire "headers already sent" on a redirect and corrupt JSON/XML/CSV payloads. The dumper selection was tightened (HtmlDumper only when Accept contains html, fixed in the 7.4.8 / 8.0.8 patch releases), but a dump in a JSON body still breaks the client — use the dump server there.VAR_DUMPER_FORMAT=server silently swallows dumps if no server is listening. If dumps "disappeared", check that env var first and that php artisan dump-server is running. Also remember config:cache/.env changes require a restart in some setups.VarDumper::setHandler() in an environment check and keep it in register()/boot() of a provider that's local-only.HtmlDumper inlines <style>/<script>; a strict Content-Security-Policy blocks them and you get unstyled, uncollapsible output. Newer releases (8.1+) forward a CSP nonce to dump() instead of disabling CSP — upgrade rather than loosening the policy.CliDumper::setColors(true) if you're capturing logs and want ANSI preserved.->toArray(); dumping a full collection can also trigger lazy-loading you didn't intend.&1 markers and *RECURSION* — that's correct output, not a bug to work around.dump() goes to worker stdout, which is often invisible under Supervisor. The dump server or Log::info() are the reliable channels.AbstractDumper::setDumpHeader('') removes the file/line banner — handy when you're dumping in a loop and the header dominates. HtmlDumper::setTheme('light'|'dark') fixes output that's unreadable against your app's theme.…, #items) are limits, not errors. Raise the cloner caps for one-off deep inspections instead of assuming the data is missing.DatePeriod division-by-zero, DOM/CurlHttpClient casting, and terminal escape-sequence injection via context strings. Laravel's constraint will move you along, but a composer update symfony/var-dumper on a stale lockfile is worth it.dump() in code that can reach production. It exposes secrets (auth models, request payloads, config) and pollutes responses. Add a CI grep for \b(dd|dump)\( outside tests, or rely on a pre-commit hook.How can I help you explore Laravel packages today?