Snippet API
This page lists everything a snippet can use in Runlet: how it runs, what shows up in the output, magic comments, the Runlet\ functions, the run inspector, snippet inputs, and the variables your framework hands you.
The same API works on every target (the Laravel sandbox, local projects, Docker containers, and SSH servers) and on PHP 7.4 and later. To boot an application Runlet doesn't detect, or to add inspector sections of your own, write a project driver.
How a Snippet Runs
- Each run is a fresh PHP process. The application boots, then your snippet runs. Variables don't carry over between runs. When you want them to, Library ▸ Open REPL opens the target's Tinker, PsySH, or
php -ain a terminal. <?phpis optional. Without it, Runlet adds it on the first line, so line numbers stay as you see them.- The last expression is the result, as in Tinker.
User::count()on the last line shows its value. An explicitreturnworks too, and the final semicolon may be left out. A last statement that isn't an expression, such asechoorforeach, gives no result. - Run Selection (⇧⌘R) runs only the selected code, and lines in the output point back at the editor's lines.
- Strict types. Turn on Settings ▸ General ▸ Running ▸ Declare strict_types=1 for every run to add
declare(strict_types=1);on the first line. Projects and Docker profiles can override it, and code that declaresstrict_typesitself is left alone. - Working directory. The snippet runs in the project's folder: the container's or the server's for Docker and SSH targets.
getenv('RUNLET_RUN_ID')is the run's id.
use App\Models\User;
$users = User::where('active', true)->get();
$users->pluck('email')The last line is the result: a collection of emails, with no return or semicolon needed.
Running Code covers Run, Stop, and the output pane.
Output
| In the snippet | In the output |
|---|---|
The last expression, or return | A Result card: an expandable tree, a table for rows and collections, and string viewers for JSON, long text, images, and HTML. |
echo, print, printf, writes to STDOUT | Printed output, as written. Writes to STDERR are orange. Raw has exactly what PHP wrote, up to 8 MiB per run. |
dump($a, $b), dd(...) | A dump card per value, with the line that called it. dd() ends the run, which still counts as completed. They use your project's VarDumper, or Runlet's own dump() and dd() when there is none. |
| An uncaught exception or error | An error card: the class, the stage (runtime, parse, bootstrap, or fatal), the message, Go to line N (or the file and line), the source where it failed, its cause, and the stack trace. The editor marks the line, and the run fails. |
exit() or die() | The run ends. It fails when the exit code isn't 0. |
\Runlet\notice(), warning(), error() | Notice, warning, and error cards with the calling line. The run goes on and doesn't fail (below). |
A returned or dumped mailable, mail notification, view, Htmlable, Renderable, or HTML Symfony response | An HTML preview next to the value tree, with JavaScript off, no navigation, and no remote loads unless you allow images for that preview. A project driver can add types; Settings can turn previews off. See Previews. |
Results and dumps are bounded: depth 8, 200 entries per level, 64 KiB per string, and 2 MiB per value. A list of Eloquent models shows up to 1,000 models in Values, within the same size limit. Runlet reads values without calling your code: no getters, __toString(), __debugInfo(), or __get(). The one exception is your project driver's casters, which show your own types the way you declare. The Structured, Plain, and Raw views show the same run; Plain and Raw, Copy Output, and Save Output As… always have everything.
Source Excerpts
An error card shows the code where the error happened: about five lines around it, numbered and coloured like the editor, with the failing line marked. When Go to line N is your snippet's line but the error was thrown in a file, the card says Thrown in that file and shows its lines.
- Stack frames. Each frame with source has a ▸ that shows its lines. The first frame in a project file starts open (unless the card already shows it). The snippet's own frames, vendor code, and files outside the project stay closed until you open them.
- Where the lines come from. The snippet's lines are the code that ran: for Run Selection, the selection, numbered as in the editor. Files are read on your Mac: from the project folder for local projects and the sandbox, and from the profile's local folder for Docker and SSH targets. Those excerpts are marked local copy, because the file in the container or on the server may differ; the tooltip says where it is there.
- Files that aren't on your Mac, such as a compiled view that was cleared, a server path outside the profile's folder, or a profile without a local folder, show Source not available here with the path, and the tooltip says why. A file that got shorter since the run says so too.
- Clicking a line. A snippet line moves the caret there. A project file opens at that line in your external editor (Settings ▸ Editor ▸ External Editor). Vendor code, files outside the project, and any file when no editor is set open in the read-only peek of Go to Definition. Nothing runs.
- Bounds. Runlet reads only the lines it needs, in the background, at most 8 MB into a file and 300 characters per line, once per run. Excerpts appear only in Structured output: Plain and Raw, Copy Output, and AI clients' results don't have them.
Notices, Warnings, and Errors
Three functions put a card in the output, with the snippet line that called them, without ending the run:
\Runlet\notice('Imported 120 rows', ['skipped' => 3]);
\Runlet\warning('Cache is cold', ['store' => 'redis']);
try {
$client->sync();
} catch (\Throwable $e) {
\Runlet\error($e, ['client' => $client->id]);
}
\Runlet\error('3 orders have no customer');| Function | Card |
|---|---|
\Runlet\notice(string $message, array $context = []): void | A blue notice card. |
\Runlet\warning(string $message, array $context = []): void | An orange warning card, like Runlet's own warnings. |
\Runlet\error(string|\Throwable $message, array $context = []): void | A red error card marked not fatal. With a Throwable, the card shows its class and message, where it was thrown (when that isn't the calling line) with its source, its cause, and its stack trace. |
The run inspector has the same three as methods: \Runlet\Inspector::current()->notice($message, $context), ->warning(…), and ->error(…). They behave exactly like the functions.
- The line. The card links to the snippet line that called it. When no snippet line is on the stack, such as in a driver's
bootstrap(), it links to the first project file outsidevendor/, which opens in your editor. - Context.
$contextshows under the message as a collapsed value, read without calling your code and bounded: depth 6, 100 entries per level, 16 KiB per string, and 512 KiB per value. - Never a failure. An error card doesn't make the run fail: Run History, notifications for long runs, and AI clients don't count it as an error, and the editor doesn't mark the line. The run's footer counts warnings and errors ("2 warnings, 1 error"); notices aren't counted.
- They never throw.
error()takes anything: a string, aThrowable, or any other value as short text (42,array(3), a class name, or aStringable's text).notice()andwarning()are typed, so PHP checks their arguments as for any function. - Always shown. The cards show whether the run inspector is on or off, and they aren't inspector records.
- Bounds. A message keeps its first 16 KB, and the card notes the rest. A run shows at most 200 of these cards and 8 MB of them; a notice at the end says how many were left out.
- Secrets. A saved database connection's password is replaced by
•••in the message, the context, and aThrowable's details. - Plain text. Copy Output and Plain read
⚠︎ Warning (line 3): Cache is cold, followed byCaused by …andContext: …lines when there are any. Markdown export has the same. - AI clients get each card as a line, such as
Warning (line 3): Cache is cold, and as a structured message, not as an error. See AI clients.
TIP
The functions return nothing, so as the last line of a snippet they make the result null. Put the value you want to see last.
trigger_error() keeps PHP's behaviour, or your framework's: Laravel turns E_USER_WARNING and E_USER_NOTICE into exceptions, for example. Runlet doesn't turn it into cards; use \Runlet\warning() instead.
Magic Comments
Magic comments show values in the editor while the code runs:
| Comment | Shows |
|---|---|
//? at the end of a line | The line's value. ✓ on a line without a value, when it's reached. |
/*?*/ after an expression | That expression's value. |
/*?->count()*/ | A projection of the value before it; the code still gets the value itself. |
/*?.*/ | Milliseconds since the previous /*?.*/, or since the snippet started. |
Magic Comments has every form, loops, and the places Runlet skips.
Runlet Functions
The runner defines these functions in the Runlet namespace on every target:
| Function | What it does |
|---|---|
\Runlet\notice(), \Runlet\warning(), \Runlet\error() | Cards in the output (above). |
\Runlet\bench($callables, int $iterations = 1000, ?string $label = null, ?float $seconds = null): array | Measures a callable, or up to 20 labelled callables side by side, and shows a benchmark card: mean, median, p95, min, max, operations per second, memory, and a histogram. Returns the numbers in milliseconds. Shown even with the inspector off. See Benchmarks. |
\Runlet\explainPlan($rows, $connection = null, ?string $connectionName = null) | Shows EXPLAIN rows as Runlet's plan card: a tree with full scans highlighted, and the database's own output under Raw. Rows it can't read as a plan come back as they are. See SQL Explain. |
use Illuminate\Support\Str;
Runlet\bench(fn () => Str::slug('Ada Lovelace'), 5000, 'Str::slug()');The functions exist only when Runlet runs the code. Code that should also run elsewhere can check first: function_exists('Runlet\bench').
The Run Inspector
\Runlet\Inspector::current() returns the run's inspector, or null outside a snippet run (for example, while Runlet lists a project's commands). What a snippet records shows in the inspector's sections next to the output, with the snippet line:
$inspector = \Runlet\Inspector::current();
$inspector->record('Debug', 'cart', $cart);
$inspector->log('info', 'Checked out', ['order' => $order->id]);| Method | Purpose |
|---|---|
notice(string $message, array $context = []), warning(…), error(string|\Throwable $message, array $context = []) | Cards in the output, like the functions above. Shown with the inspector off too. |
record(string $section, string $title, $value) | Any value in a section of your own, shown like a dump (bounded, no methods called). |
log(string $level, string $message, array $context = [], ?string $channel = null) | One message in Log. |
html(string $title, string $html, string $section = 'HTML') | Rendered HTML, previewed in a locked-down web view. |
query(string $sql, array $bindings = [], ?float $ms = null, ?string $connection = null, array $details = []) | One statement in Queries. |
mail($message, array $details = []) | One message in Mail: a Symfony Mime Email, a SwiftMailer message, or an array. |
watchPdo(\PDO $pdo, string $connection = 'pdo'): bool | Records a PDO connection's prepared statements. |
http(array $request), job(array $job), event(string $name, $payload = null) | One request in HTTP (credentials redacted), one job in Jobs, one event in Events. See Recording From Your Driver. |
shouldRecord(string $section): bool, shouldRecordHttpBodies(): bool | Whether the run records HTTP, JOBS, or EVENTS, and HTTP bodies (the settings). |
section(string $section) | Shows a section even when nothing is recorded in it. |
isEnabled(): bool | Whether the inspector records this run. |
shouldInterceptMail(): bool, interceptingMail(), cannotInterceptMail(string $reason) | Mail interception, for drivers. |
once(string $key): bool, atFinish(callable $callback), location(): array | Helpers for hooks: attach once, run something when the run finishes, and where the running code came from. |
QUERIES, MAIL, LOG, HTML, HTTP, JOBS, EVENTS | The built-in sections' names. |
No method throws. With the inspector off (Settings ▸ General ▸ Run Inspector ▸ Record queries, mail, and logs), the recording methods do nothing and watchPdo() returns false; notice(), warning(), error(), and bench() still show. Limits and the hooks drivers use are in Run inspector.
Snippet Inputs
A snippet can declare inputs in its docblock, and Runlet asks for their values when you open it:
/**
* @label Refund order
* @input int $orderId "Order ID"
* @input string $reason "Reason" = "duplicate" {duplicate, fraudulent}
*/Opening it puts $orderId = 1042; and the other values at the top of the code, and never runs it. Snippet Inputs has the types, defaults, and choices.
Driver Variables
The driver that boots your project puts variables in the snippet's scope:
| Project | Variables |
|---|---|
| Laravel, Lumen, Laravel Zero | $app |
| WordPress | $wpdb |
| Symfony | $kernel, $container |
| Composer projects, plain PHP | none |
| A project driver | what its variables() returns |
A snippet can reassign them without affecting the driver, and completion knows their types. See Variables and reporting.
Completion
\Runlet\ completes in the editor, with hover and signature help, in every PHP tab. That includes Docker and SSH targets without a local checkout.