sebastian/code-unit-reverse-lookup
Looks up which function or method a given line of code belongs to, a small utility from the PHPUnit ecosystem by Sebastian Bergmann. Install it via Composer as a project or dev dependency. Now a low maintenance project.
sebastian/code-unit-reverse-lookup is a tiny, framework-agnostic PHP library that answers one question: given a file and a line number, which function or method does that line belong to? It is not a Laravel package — there is no service provider, facade, config file, or published assets. In a Laravel codebase you will mostly encounter it as a transitive dev dependency of phpunit/php-code-coverage.
First, check whether it is already present:
composer show sebastian/code-unit-reverse-lookup
composer why sebastian/code-unit-reverse-lookup
If it is only needed for local tooling or tests, install it as a dev dependency:
composer require --dev sebastian/code-unit-reverse-lookup
The first practical use case is mapping an uncovered or suspicious line to its owning method. The API is minimal:
use SebastianBergmann\CodeUnitReverseLookup\Wizard;
$wizard = new Wizard();
echo $wizard->lookUp(
base_path('app/Http/Controllers/UserController.php'),
42
);
// e.g. App\Http\Controllers\UserController::index
Look first at vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php; the implementation is small enough to read end-to-end. There is no Laravel-specific documentation, so treat the source and its tests as the reference.
1. Wrap it in a small application service. Since the class is final and framework-agnostic, keep it behind your own boundary so you can swap the implementation later:
namespace App\Support;
use SebastianBergmann\CodeUnitReverseLookup\Wizard;
final class CodeUnitLocator
{
private Wizard $wizard;
public function __construct()
{
$this->wizard = new Wizard();
}
public function forLine(string $file, int $line): string
{
$file = realpath($file) ?: $file;
return $this->wizard->lookUp($file, $line);
}
}
Register it as a singleton in AppServiceProvider when used repeatedly, because the wizard caches per-file parsing:
$this->app->singleton(\App\Support\CodeUnitLocator::class);
// Optional: only expose it in local/testing environments.
if ($this->app->environment(['local', 'testing'])) {
$this->app->singleton(\App\Support\CodeUnitLocator::class);
}
2. Annotate custom coverage output. A common pattern is an Artisan command that reads a coverage report and enriches each line with its method name:
$wizard = new Wizard();
foreach ($uncoveredLines as $file => $lines) {
foreach ($lines as $line) {
$unit = $wizard->lookUp($file, $line);
$this->line(sprintf('%s:%d — %s', $file, $line, $unit));
}
}
This is useful for “which methods are least covered?” reports that are more actionable than raw line percentages.
3. Enrich local exception context. In a local-only error handler or debugging endpoint, map the exception’s getFile() and getLine() to a method name. Do not enable this in production unless you deliberately want that metadata exposed.
4. Custom PHPUnit extensions or test hooks. If you write a PHPUnit extension that reports uncovered code after a run, reverse lookup lets you group failing or uncovered lines by method rather than by raw line number. This is the package’s natural habitat: PHPUnit’s own coverage tooling uses it for exactly this purpose.
5. Static inspection commands. Any internal command that scans app/ and reports “where is this line?” — for example, a tool that flags very long methods by inspecting coverage or token data — can use the wizard as one small piece rather than building a parser.
Integration tips:
realpath() or base_path() to normalize.Wizard instance per process to benefit from its internal per-file cache.require-dev.eval()-ed code, files with syntax errors, and dynamically declared functions may produce misleading or empty results.Class::method). Avoid regexing it apart; if you need reflection, resolve the class separately.Wizard for every line defeats that cache and can be slow on large files.$result = (new Wizard())->lookUp(__DIR__ . '/fixtures/Sample.php', 12);
$this->assertStringContainsString('::sampleMethod', $result);
nikic/php-parser or the APIs exposed by php-code-coverage; keeping it behind CodeUnitLocator makes that swap cheap.How can I help you explore Laravel packages today?