Sonda inserts probes into your source before it loads, the way Istanbul does for JavaScript. The suite runs at its normal speed, and every decision has to show both outcomes.
A Laravel 13 application with Filament, Pest 4 and Postgres: 4,514 tests, measured on the same machine. Xdebug with path coverage did not finish in an hour.
Memory: 308 MB without coverage, 317 MB with Sonda, 640 MB with Xdebug lines only. With ParaTest and 10 workers: 42s without coverage, 40s with Sonda.
Built for a merge gate: 100% of the lines and branches of what the merge request changed, in the time the suite already takes.
Every outcome of if, loops, match, ?:, ??, ?-> and short-circuit operators is a branch with its own probe.
Nothing to install on the runner. Any PHP 8.4+ with your vendor/ is enough; the report comes from php-code-coverage.
An if without else still needs both outcomes tested. Sonda synthesizes the else and counts it.
--changed measures the files your branch touched and fails if a line or branch of them stays uncovered.
--affected runs just the tests that executed the changed files, from a map the previous run saved.
Compiled templates are instrumented and mapped back to the view, line and branch, including custom directives.
No opcode interception and no path enumeration, which is what makes Xdebug slow. A probe is one array assignment, and the instrumented copy keeps every line number.
Each file of your phpunit.xml source gets a probe at every branch outcome. Copies are cached by file hash, so a second run instruments nothing.
A bootstrap registers an autoloader ahead of Composer's that loads the copies. PHPUnit, Pest, ParaTest and artisan test run untouched; each worker saves what it hit.
Hits become a php-code-coverage report: HTML, Clover, Cobertura for the GitLab diff and Code Quality annotations on the uncovered lines.
public function total(): int
{
if ($this->items === []) {
return 0;
}
return $this->sum() + $this->shipping;
}
public function total(): int
{
$h = &Hits::$current['a1b2c3'];
$h[1] = 1;
if ($this->items === []) {
$h[2] = 1;
return 0;
} else { $h[3] = 1; }
$h[4] = 1;
return $this->sum() + $this->shipping;
}
Composer, then sonda test from the project root. PHP 8.4+, PHPUnit 12 or Pest 4.
Read the getting started guide