Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpunit Syntax Coverage Laravel Package

phpfui/phpunit-syntax-coverage

A PHPUnit extension that measures PHP syntax coverage, showing which language constructs and parser nodes your test suite actually exercises. Use it to spot untested syntax like new operators, match expressions, or traits, then close those gaps with focused tests.

View on GitHub
Deep Wiki
Context7

Getting Started

What it gives you. PHPUnit's code coverage tells you which lines inside loaded files ran. It says nothing about files that no test ever touched — those show up as 0% (or vanish entirely if they're outside <source>). phpfui/phpunit-syntax-coverage closes that gap: it hooks into the PHPUnit run and asserts that every PHP file in your source tree was at least loaded by the suite. Think of it as "no orphan files" rather than "no uncovered lines."

Install and pin against your PHPUnit major. This is the first thing to check, because extension wiring changed in PHPUnit 10:

composer require --dev phpfui/phpunit-syntax-coverage
composer show phpfui/phpunit-syntax-coverage   # inspect the phpunit/phpunit constraint

If you're on PHPUnit 9, the integration is a listener (<listeners><listener class="..."/></listeners>). On PHPUnit 10/11 it's an extension (<extensions><bootstrap class="..."/></extensions>). The package is small and single-maintainer — confirm the exact class name and parameter keys by opening src/ before copying any snippet, including the one below, which shows the shape rather than a guaranteed signature:

<!-- phpunit.xml -->
<extensions>
    <bootstrap class="PHPFUI\PHPUnitSyntaxCoverage\Extension">
        <parameter name="src" value="src"/>
    </bootstrap>
</extensions>

First use case: a legacy repo audit. Point it at src/, run the full suite once, and read the failure output as a work list. You'll typically get three buckets: genuinely dead files, files loaded only via require in a bootstrap (so they pass but aren't really tested), and generated/vendored code you forgot to exclude. That last bucket is your exclusion list, and it's the fastest win.

Where to look first. README → composer.json (version constraints tell you which PHPUnit API it targets) → src/. At this size the source is the documentation; reading the extension class takes five minutes and answers every question the README doesn't.

Implementation Patterns

Baseline-then-gate. A 200-file legacy codebase will produce 60+ orphans on day one, so don't make the first PR red. Either the package supports an ignore/exclude list, or you wrap it:

# once, to capture the current state
vendor/bin/phpunit 2>&1 | grep -i 'not covered\|missing' > syntax-baseline.txt

Then commit the baseline and diff against it in CI, failing only on new entries. If the package has no baseline mechanism, implement it in your CI script — don't fork unless you have to.

Run it as part of the normal suite, never standalone. The check is only meaningful if the whole suite ran in the same process. So:

"scripts": {
    "test": "@php vendor/bin/phpunit",
    "test:full": ["@test", "@phpstan", "@phpcs"]
}

Never gate on phpunit --filter=SomeTest — a partial run will report every file outside that filter as orphaned.

Split the scan by tree in monorepos. A separate phpunit-src.xml scoped to src/ (with tests/ excluded from the scan) keeps runtime sane and keeps the failure message readable:

<parameter name="src" value="src"/>
<parameter name="exclude" value="src/Generated,src/database/migrations"/>

Pair it with a class_exists() smoke test. The cheapest fix for a reported orphan that is real code is a single generated test that autoloads every class. That also catches PSR-4 mismatches, which is a nice side effect.

foreach (glob(__DIR__ . '/../src/**/*.php') as $file) {
    $class = /* FQCN from path */;
    $this->assertTrue(class_exists($class), $class);
}

Use it as a deletion tool, not just a gate. The highest-value workflow isn't the CI check — it's running it once a quarter against a mature codebase and deleting the files it flags. Nothing else in the PHP toolchain finds genuinely unreachable source.

Wire it into the shared base TestCase if the package ships a trait or base class rather than a pure extension. Put the trait on tests/TestCase.php once and every test class inherits the behaviour — that's less config to keep in sync than XML.

Gotchas and Tips

  • PHPUnit major compatibility is the #1 failure mode. "Class not found" on the bootstrap almost always means you're on the wrong side of the 9→10 extension API split. Check the constraint before anything else.
  • False positives from non-class files. Config returns, constants files, and pure-side-effect scripts are never "loaded" by a unit test in any meaningful sense. Expect them in the report and allowlist them deliberately.
  • Exclusions must cover more than you think: vendor/, generated code, migrations, stubs, fixtures, tests/ itself. The default may not exclude anything.
  • Interaction with @codeCoverageIgnore and <source> filters. Syntax coverage generally bypasses these — which is exactly the point, but it surprises people who assume a file marked ignored is invisible to every tool.
  • Non-determinism in CI. PHPUnit's result cache can make runs differ between machines; use --do-not-cache-result when you're debugging why CI fails and local passes.
  • Silent no-ops. If adding the extension changes nothing, first check you edited the right phpunit.xml (there are often two), then that the src parameter actually resolves. Run with --debug / --display-all-issues depending on version to see extension output.
  • Two stars, zero dependents. Treat it as a vendored utility, not a framework dependency. Pin the exact version in composer.json, and if you need a baseline file or richer reporting, fork it — the surface area is small enough that maintaining a fork is cheaper than waiting for upstream.
  • Extension points to know: the PHPUnit\Runner\Extension\Extension interface (bootstrap(Configuration, Facade, ParameterCollection)) plus subscribers (Facade::registerSubscriber()). If you need custom behaviour, write your own tiny extension that reuses the package's file iterator rather than patching the package.
  • Don't run it in parallel with paratest assumptions. Whether it aggregates across worker processes depends on implementation; verify before relying on it in a parallelised pipeline.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core