Send faithful, typed value dumps and structured debug messages (exceptions, SQL queries, HTTP calls, logs, models, …) to the Dumpio viewer. HTTP-first, fire-and-forget, never breaks the host app — every call swallows its own errors and silently drops if the viewer isn't running.
Built to the authoritative client contract in ../BUILDING.md.
For a thorough, example-driven walkthrough see docs/GUIDE.md.
composer require dumpio/clientConfiguration is read from the environment, or override it at runtime:
| Option | Env var | Default |
|---|---|---|
| host | DUMPIO_HOST |
localhost |
| port | DUMPIO_PORT |
21234 |
| token | DUMPIO_TOKEN |
"" |
| enabled | DUMPIO_DISABLE (set ⇒ off) |
true |
use Dumpio\Dumpio;
Dumpio::configure(['host' => 'localhost', 'port' => 21234, 'token' => 'secret']);dio($user, 'user')->green(); // fluent builder (auto-sends); see below
dumpio($user, 'user', 'blue'); // tap-style: sends and returns $user, like tap()
ddio($a, $b); // dump every arg, then die
// or: Dumpio::dump($user, 'user'); Dumpio::dd($a, $b);dio() returns the fluent builder so you can chain color/label/flood control;
dumpio() returns the value so you can wrap an expression. Pick by need.
The var serializer uses Reflection, so member visibility
(public/protected/private), class names, typed/uninitialized properties and
enums are preserved. It breaks cycles via ref nodes and bounds
depth/items/string length. The calling file:line is captured automatically.
It also recognises a few common shapes and renders their logical value
instead of raw internals: DateTimeInterface becomes a formatted date, an
Eloquent model becomes its casted/visible attributes (via
attributesToArray()), and a Laravel Collection becomes its items. These
checks are by class name, so the core stays framework-agnostic and they are
inert when Laravel is absent.
dio() (and Dumpio::make()) return a chainable builder. The dump ships on
->send() or automatically when the builder goes out of scope, so ->send() is
optional:
dio($user)->red()->label('user')->channel('auth'); // auto-sends
Dumpio::make($payload)->purple()->send(); // explicit sendColors: red() yellow() blue() gray() purple() pink() green() (or flag('…')),
plus label(), channel(), and conditional when($bool) / unless($bool).
Helpers keep loops from flooding the viewer (keyed by call-site file:line,
per process — or by an explicit name):
foreach ($rows as $row) {
dio($row)->once(); // only the first iteration is sent
dio($row)->limit(5); // at most 5 are sent
dio($row)->count(); // one live-updating entry, "×N" in the viewer
dio($row)->count('rows'); // share one counter across call-sites
}Time a block with a stopwatch (ships a measure dump with elapsed ms + memory):
$sw = Dumpio::stopwatch('import');
$sw->lap('parsed'); // intermediate split, keeps running
$sw->stop('done'); // final timingOn Laravel, ->dio() and ->ddio() are registered as macros on query builders
and collections, so you can drop a dump mid-chain without breaking it — it
ships the current state and returns $this:
User::query()
->where('name', 'John')
->dio() // → query dump (SQL + bindings so far)
->whereDate('email_verified_at', '2024-02-15')
->dio() // → query dump (with the extra clause)
->first();
collect($users)->dio('after filter'); // → var dump, returns the collection->ddio() is the dump-and-die variant. Works on Eloquent\Builder,
Query\Builder (DB::table(...)) and Support\Collection. Disable with
DUMPIO_REGISTER_MACROS=false.
Every helper is a static method on Dumpio (fire-and-forget). $opts always
accepts envelope overrides: flag, channel, origin.
| Helper | One-liner |
|---|---|
Dumpio::exception(\Throwable $e, array $context = [], array $opts = []) |
Dumpio::exception($e, ['user' => ['id' => 1]]); |
Dumpio::query(string $sql, array $bindings = [], ?float $timeMs = null, array $opts = []) |
Dumpio::query('select * from users where id = ?', [1], 1.8); |
Dumpio::http(string $method, string $url, ?int $status = null, array $opts = []) |
Dumpio::http('POST', '/api/users', 201, ['body' => $payload, 'responseTime' => 120]); |
Dumpio::log(string $level, string $message, array $details = [], array $opts = []) |
Dumpio::log('warning', 'Auth failed', ['ip' => $ip]); |
Dumpio::model(string $class, array $attributes, array $opts = []) |
Dumpio::model(User::class, $user->getAttributes(), ['exists' => true]); |
Dumpio::collection(array $items, array $opts = []) |
Dumpio::collection($users, ['message' => 'users']); |
Dumpio::table(array $columns, array $rows, array $opts = []) |
Dumpio::table(['id', 'name'], [[1, 'Ada'], [2, 'Linus']]); |
Dumpio::measure(string $name, float $timeMs, array $opts = []) |
Dumpio::measure('render dashboard', 84.2, ['memory' => 2097152]); |
Dumpio::performance(array $metrics, array $opts = []) |
Dumpio::performance(['db_queries' => 12], ['breakdown' => ['database' => 120]]); |
Dumpio::event(string $event, array $opts = []) |
Dumpio::event('order.completed', ['entity' => 'order', 'data' => ['total' => 299.9]]); |
Dumpio::trace(?string $label = null, array $opts = []) |
Dumpio::trace('reached checkout'); — current call stack as a trace dump |
Dumpio::memory(?string $label = null, array $opts = []) |
Dumpio::memory('after import'); — current / peak / limit memory snapshot |
Dumpio::html(string $html, array $opts = []) |
Dumpio::html($renderedView, ['message' => 'Checkout page']); |
Dumpio::mail(array $mail, array $opts = []) |
Dumpio::mail(['subject' => 'Hi', 'to' => ['ada@x.io'], 'html' => $html]); |
Dumpio::mailable(object $mailable, array $opts = []) |
Dumpio::mailable(new OrderShipped($order)); |
html / mail render foreign markup in the viewer's sandboxed iframe (no
scripts, remote resources blocked by default); mail adds a subject/from/to
envelope. See the guide.
Flags are picked automatically where it helps: exceptions are red, queries
purple, HTTP by status (≥500 red, ≥400 yellow, ≥300 blue, else green),
logs by level (error red, warning yellow, info blue).
Two thin global wrappers are also autoloaded for the most common cases:
dumpio_exception($e, ['user' => ['id' => 1]]);
dumpio_query('select * from users', [], 1.2);The service provider is auto-discovered — no manual registration. It reads
config('dumpio.*'), configures the static client, and (opt-in) forwards
queries and exceptions.
Publish the config:
php artisan vendor:publish --tag=dumpio-configconfig/dumpio.php:
return [
'host' => env('DUMPIO_HOST', 'localhost'),
'port' => (int) env('DUMPIO_PORT', 21234),
'token' => env('DUMPIO_TOKEN', ''),
'enabled' => env('DUMPIO_ENABLED', env('APP_DEBUG', false)), // off in prod
'listen_queries' => env('DUMPIO_LISTEN_QUERIES', false), // DB::listen → query dumps
'listen_exceptions' => env('DUMPIO_LISTEN_EXCEPTIONS', false), // reported exceptions → exception dumps
'intercept_dumps' => env('DUMPIO_INTERCEPT_DUMPS', false), // dump()/dd() → the viewer
'listen_models' => env('DUMPIO_LISTEN_MODELS', false), // Eloquent created/updated/deleted/restored → model dumps
'listen_cache' => env('DUMPIO_LISTEN_CACHE', false), // cache hit/miss/written/forgotten → event dumps
'listen_jobs' => env('DUMPIO_LISTEN_JOBS', false), // queue job processing/processed/failed → event dumps
'listen_events' => env('DUMPIO_LISTEN_EVENTS', false), // application (non-framework) events → event dumps
'listen_mail' => env('DUMPIO_LISTEN_MAIL', false), // outgoing email → mail dumps (rendered preview)
'intercept_mail' => env('DUMPIO_INTERCEPT_MAIL', false), // + swallow the real send in dev (Mailpit-style)
'listen_http_client' => env('DUMPIO_LISTEN_HTTP_CLIENT', false), // outgoing Http:: calls → http dumps
'listen_livewire' => env('DUMPIO_LISTEN_LIVEWIRE', false), // Livewire component lifecycle → event dumps
'listen_livewire_verbose' => env('DUMPIO_LISTEN_LIVEWIRE_VERBOSE', false), // + boot/hydrate/dehydrate/destroy (high-volume, opt-in)
];When enabled is false (the default in production) the provider configures the
client as disabled and registers no listeners — a complete no-op.
Each switch below is off by default; flip the env var to forward that signal:
| Env var | What it forwards |
|---|---|
DUMPIO_LISTEN_QUERIES |
every executed SQL query (DB::listen) |
DUMPIO_LISTEN_EXCEPTIONS |
reported exceptions |
DUMPIO_INTERCEPT_DUMPS |
dump() / dd() routed to the viewer (via VarDumper::setHandler) — replaces inline rendering, and dd() still dies |
DUMPIO_LISTEN_MODELS |
Eloquent created / updated / deleted / restored as model dumps (channel models) |
DUMPIO_LISTEN_CACHE |
cache hit / missed / written / forgotten as event dumps (channel cache) |
DUMPIO_LISTEN_JOBS |
queue job processing / processed / failed as event dumps (channel jobs) |
DUMPIO_LISTEN_EVENTS |
your application (non-framework) events as event dumps (channel events) |
DUMPIO_LISTEN_MAIL |
every outgoing email (MessageSending) as a mail dump with rendered preview (channel mail) |
DUMPIO_INTERCEPT_MAIL |
with LISTEN_MAIL: also cancel the real send so dev mail lands only in Dumpio (fake-mail / Mailpit-style) |
DUMPIO_LISTEN_HTTP_CLIENT |
every outgoing Http:: request as an http dump with status + timing (global Guzzle middleware; needs guzzlehttp/guzzle) |
DUMPIO_LISTEN_LIVEWIRE |
Livewire 3 and 4 component mount / action / update / render / island / exception as event dumps (channel livewire; needs livewire/livewire) |
DUMPIO_LISTEN_LIVEWIRE_VERBOSE |
+ the high-volume Livewire points — boot / hydrate / dehydrate / destroy — on the livewire channel. Opt-in on top of LISTEN_LIVEWIRE (fires on nearly every request); works on v3 and v4 |
Config flags only seed the initial state. Any capture can be flipped on or off at runtime — handy inside a test, a Tinker session, or around a suspect block:
Dumpio::showQueries(); // start forwarding queries now
// … the code you want to inspect …
Dumpio::stopShowingQueries(); // and stop
Dumpio::showAll(); // everything on
Dumpio::stopShowingAll(); // everything offPairs exist for Queries, Exceptions, Dumps, Models, Cache, Jobs,
Events, Mail, HttpClient, Livewire and LivewireVerbose.
Point a log channel at Dumpio\Log\DumpioHandler to stream app logs into the
viewer as log dumps (level → flag colour):
// config/logging.php
'channels' => [
'dumpio' => [
'driver' => 'monolog',
'handler' => \Dumpio\Log\DumpioHandler::class,
],
],Then Log::channel('dumpio')->warning('…'), or add 'dumpio' to a stack
channel's channels list to mirror your default log.
Use the helpers anywhere, or the optional facade (also auto-discovered as the
Dumpio alias):
use Dumpio\Laravel\Facades\Dumpio;
Dumpio::query($sql, $bindings, $timeMs);
// equivalently: \Dumpio\Dumpio::query(...) or dumpio_query(...)Set DUMPIO_LISTEN_QUERIES=true to mirror every executed query into the viewer
via DB::listen, and DUMPIO_LISTEN_EXCEPTIONS=true to forward reported
exceptions through the framework's exception handler.
Add the bundle (dev only is recommended):
// config/bundles.php
return [
// …
Dumpio\Symfony\DumpioBundle::class => ['dev' => true],
];The bundle registers Dumpio\Symfony\EventSubscriber\ExceptionSubscriber, which
forwards every kernel.exception to the viewer as an exception dump with the
current request context. It reads DUMPIO_HOST / DUMPIO_PORT / DUMPIO_TOKEN
/ DUMPIO_DISABLE from the environment.
The bundle adds more auto-instrumentation when the relevant component is present:
| Condition | What it forwards |
|---|---|
| always | kernel.exception → exception dumps with request context |
| Doctrine DBAL 4 | every executed SQL → query dumps (channel database); SQL + bindings + timing |
| symfony/messenger | Messenger sent / received / handled / failed → event dumps (channel messenger) — the Symfony equivalent of Laravel's queued-job listeners |
symfony/mailer + DUMPIO_LISTEN_MAIL set |
every outgoing email (MessageEvent) → mail dumps with rendered preview (channel mail) |
symfony/http-client + DUMPIO_LISTEN_HTTP_CLIENT set |
every outgoing request → http dumps with status + timing (non-blocking http_client decorator, channel http) |
symfony/cache + DUMPIO_LISTEN_CACHE set |
app-cache hit / missed / written / forgotten / invalidated → event dumps (channel cache; pure-passthrough cache.app decorator) |
twig/twig + DUMPIO_LISTEN_VIEWS set |
every rendered template → measure dumps with render time (channel views; via a Twig ProfilerExtension) |
DUMPIO_INTERCEPT_DUMPS set |
dump() / dd() routed to the viewer (via VarDumper::setHandler); dd() still dies |
The runtime toggles (Dumpio::showQueries() …) and the Monolog handler
(Dumpio\Log\DumpioHandler) documented above work the same under Symfony.
Symfony's
MessageEventhas no cancel hook, so there is nointercept_mailhere — the subscriber only captures mail. To keep dev mail from actually sending, pointMAILER_DSNat anull://nulltransport.
You can also call the static client or helpers from anywhere in your code:
\Dumpio\Dumpio::query($sql, $bindings, $timeMs);
dumpio($entity, 'entity');See ../BUILDING.md for the full wire contract and the shared
var tree format.