Dev Bar
The Via Dev Bar is a debug overlay built into php-via, like the Symfony Web Debug Toolbar or Laravel Debugbar, but built around php-via's signals, scopes and SSE stream. It goes into every page without configuration and streams traces and logs as they happen.
It is running on this page. Look for the via pill in the bottom-right corner and click it
open. Every panel below describes what you are looking at.
Turning it on
The Dev Bar follows dev mode: on with withDevMode(), off otherwise, and then every
/_via endpoint answers 404. withDevBar() overrides that either way:
withDevBar(false) turns it off in dev mode, and withDevBar(true) turns it on
outside dev mode, read-only, which is how this site shows it.
$config = (new Config())
->withDevMode($isDev)
->withDevBar(true) // also outside dev mode, read-only
->withDevBarOptions(traces: 100); // traces kept per worker (default 100)/_via/scopes and the console list them, and the server's request counts and runtime
figures in the Stats panel. Session scopes show a hash, never the session
cookie, and traces show [redacted] for attributes named like a password, token, cookie or
session. Outside development,
turn it on only for a demo or for a deployment that only trusted people reach, such as one behind an
admin login.
With the Dev Bar off, no trace is recorded and every capture call does nothing. When middleware sets the
via.csp_nonce request attribute, the Dev Bar's stylesheet and script carry the nonce, as
via_head's tags do (see Content-Security-Policy).
Each tab with the Dev Bar holds two streams, /_sse and /_via/stream. Over
HTTP/1.1 a browser opens six connections per host, so with three tabs of one site open, the third tab's
actions wait until another tab closes. Over HTTP/2 all streams share one connection: serve the site with
withCertificate(), or behind a proxy such as Caddy.
The Dev Bar is separate from the Live Signals panel of php-via's default shell, which shows the page's signals in a sidebar in dev mode only.
The seven panels
| Panel | Shows |
|---|---|
| Traces | A waterfall of every page load, action and broadcast: nested spans with proportional bars, durations and attributes. |
| Signals | This page's named signals, grouped by scope, with their live values. Editable when writes are on. |
| SSE | The wire: every datastar-fetch event, with the patch it carried (elements or signals) expandable inline. |
| Request | Route, context id and the request span's attributes (method, status, byte size). |
| Scopes | The scopes of the worker that answered, named at the top, with the contexts in each and the connection counts: who shares what. With several workers, a tab whose stream is on another worker is not in the list. |
| Stats | Every figure of getStats()->getAll(), the broadcast tick, and the hook flags, AIO threads and event loop lag of the worker that answered. See Server statistics. |
| Logs | Server log lines, filtered by level, plus errors from the browser. See Server logs. |
The ⟲ button in the panel header clears the traces and SSE events it shows. In dev mode it also clears the worker's trace buffer, for everyone who watches that worker; outside dev mode the buffer stays.
Adding your own spans
php-via traces the request, the action, the render and the broadcast by itself. To time your own work, a
database query, an HTTP call or an expensive computation, wrap it in $c->span(). The
name's prefix before the first dot picks its colour.
$issues = $c->span('db.list_issues', fn () => $repo->all(), ['limit' => 50]);
// Annotate the span that is open, from anywhere in the action:
$c->traceAttribute('cache.hit', false);
A data layer with no Context at hand reaches the tracer directly. It adds to whatever trace
is open in the current coroutine, and is null while the Dev Bar is off:
use Mbolli\PhpVia\Tracing\Tracer;
private static function getCell(int $row, int $col): string {
$tracer = Tracer::current();
$run = fn (): string => /* the SQLite query */ '';
return $tracer === null ? $run() : $tracer->span('db.get_cell', $run, ['row' => $row, 'col' => $col]);
}
The Spreadsheet and
Chat Room examples record db.* spans this
way. Categories map to colours:
| Category | Source | Colour |
|---|---|---|
request | page load and action root spans | gray |
render | render.regions, render.component | green |
cache | shared render hits | yellow |
db | your db.* spans | blue |
sse | broadcast fan-out | violet |
| anything else | your own spans | teal |
A component's render is a render.component span tagged with the component's namespace, so
you can see which component rendered again.
Server logs
Everything that goes through php-via's logger, $app->log(), broker errors, failing
actions and php-via's own warnings, also goes to the Logs panel as it happens. The
level filter starts at info; switch to debug for everything or to
error for failures. window.onerror events from the browser land here too.
Only lines that go through php-via's logger show up. error_log() or echo from
app code bypass it: send those through $app->log() to see them in the panel.
Editing signals
The Signals panel can write values back to the server, the way Livewire or React DevTools let you poke at state. That needs dev mode and its own switch:
$config
->withDevMode(true)
->withDevBarOptions(writes: true); // or set VIA_DEVBAR_WRITES=1withDevBarOptions(writes: true) has no
effect while dev mode is off, so withDevBar(true) on a public site stays read-only. In dev
mode, anyone who can reach the page could change ROUTE, SESSION and
GLOBAL state shared with other users, so keep writes off on a dev server others can reach.
Endpoints and the console
With the Dev Bar on, php-via serves these under /_via; with it off they all answer 404.
| Endpoint | Purpose |
|---|---|
GET /_via | The Dev Console: the Dev Bar full-screen |
GET /_via/stream | SSE stream of trace and log events |
GET /_via/scopes | JSON snapshot for the Scopes panel |
GET /_via/stats | JSON snapshot for the Stats panel |
POST /_via/signal | Signal write, refused unless writes are on |
POST /_via/reset | Clears the worker's trace buffer, refused outside dev mode |
GET /_via/devbar.js, /_via/devbar.css | The overlay's assets |
The two POST endpoints check the Origin header as actions do. The overlay is a
web component with its own shadow DOM, so your page's CSS and Datastar do not reach it, and it survives
morphs of the whole page. Open /_via for the console.
The trace and log buffers live in each worker, so with withWorkerNum() above 1 the overlay
and the console show the activity of the worker their stream reached.
Server statistics
The Stats panel reads /_via/stats every 2 s while it is open, and stops when you
switch to another panel. requests, avg_request_time, actions and
sse_connections count for the whole server: page, static file and route()
requests, actions where they run, and streams that opened. The other figures belong to the worker
that answered, which the panel names, so with several workers they change with the worker a poll
reaches. Like the other panels, it is there wherever the Dev Bar is, also outside dev mode.
In dev mode, GET /_stats returns JSON for the worker that answers: its contexts, the
connected clients, render timings (render_stats), broadcast flushes
(broadcast_stats), memory and uptime. Under runtime it lists the
hook_flags in force, the AIO thread pool (aio_worker_num,
aio_task_num) and the event loop lag, where a call that blocks the worker shows up. Outside
dev mode it answers 404, since it lists client IPs. In code, $app->getStats() returns the
same render and broadcast figures.
See also
- Development workflow: hot reload and the dev loop
- Deployment: production configuration
- Design decisions: the runtime model the traces reflect