dumpio / client
PHP client for the Dumpio dump viewer — sends faithful, typed value dumps and structured messages (exceptions, queries, HTTP, logs, models, …) over HTTP.
Requires
- php: >=8.0
Requires (Dev)
- doctrine/dbal: ^4.0
- guzzlehttp/guzzle: ^7.0
- laravel/framework: ^10.0 || ^11.0 || ^12.0
- livewire/livewire: ^3.0 || ^4.0
- monolog/monolog: ^2.0 || ^3.0
- phpunit/phpunit: ^9.6 || ^10.5 || ^11.0
- symfony/dependency-injection: ^6.0 || ^7.0
- symfony/event-dispatcher: ^6.0 || ^7.0
- symfony/http-client: ^6.0 || ^7.0
- symfony/http-kernel: ^6.0 || ^7.0
- symfony/messenger: ^6.0 || ^7.0
Suggests
- doctrine/dbal: Enables Symfony Doctrine query forwarding via the Dumpio DBAL middleware (requires DBAL 4).
- guzzlehttp/guzzle: Enables Laravel outgoing HTTP-client capture via a global Guzzle middleware.
- laravel/framework: Enables the Dumpio service provider (auto-discovered): config + auto-forwarding of queries, exceptions, dd()/dump(), and model/cache/job/app events.
- livewire/livewire: Enables the Dumpio Livewire component hook (stream component lifecycle as `event` dumps).
- monolog/monolog: Enables the Dumpio Monolog handler (stream app logs into the viewer as `log` dumps).
- symfony/http-kernel: Enables the Dumpio bundle and kernel.exception subscriber.
- symfony/messenger: Enables Symfony Messenger lifecycle forwarding (sent/received/handled/failed).
README
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.
Install
composer require dumpio/client
Configuration 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']);
The basics — var dumps
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.
Fluent builder & flood control
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 send
Colors: 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 timing
Chainable macros (Laravel)
On 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.
Typed helpers
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);
Laravel
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-config
config/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 ];
When enabled is false (the default in production) the provider configures the
client as disabled and registers no listeners — a complete no-op.
Auto-instrumentation (opt-in)
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) |
Runtime toggles
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 off
Pairs exist for Queries, Exceptions, Dumps, Models, Cache, Jobs,
Events, Mail, HttpClient and Livewire.
Logs → the viewer (Monolog)
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.
Symfony
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 + timing only, bindings are not captured |
| 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) |
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 cache pools have no built-in event system (unlike Laravel), so there is no automatic cache forwarding — call
Dumpio::event('cache.…', …)yourself if you need it.
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.