graham-campbell/analyzer
Analyzer by Graham Campbell statically checks your PHP code to ensure referenced classes actually exist, helping catch typos and missing dependencies early. Supports PHP 8.1–8.5 and integrates with PHPUnit 10–13.
composer require --dev graham-campbell/analyzer:^5.1
phpunit.xml):
<phpunit>
<extensions>
<extension class="GrahamCampbell\Analyzer\PHPUnit\AnalyzerExtension"/>
</extensions>
</phpunit>
./vendor/bin/phpunit
Create a test class extending GrahamCampbell\Analyzer\TestCase:
use GrahamCampbell\Analyzer\TestCase;
class ClassReferenceTest extends TestCase
{
protected function getPaths(): array
{
return [__DIR__.'/../src'];
}
public function testClassReferences()
{
$this->assertValidClasses();
}
}
Run the test to validate all class references in src/.
register()/boot() methods for missing classes (e.g., App\Services\PaymentService).Facade::class references in registerFacade().@property PHPDoc matches actual class properties.Example: Validating a Service Provider
use GrahamCampbell\Analyzer\TestCase;
class PaymentServiceProviderTest extends TestCase
{
protected function getPaths(): array
{
return [app_path('Providers/PaymentServiceProvider.php')];
}
public function testServiceProviderReferences()
{
$this->assertValidClasses();
}
}
Override getIgnored() to exclude specific classes (e.g., third-party or dynamic classes):
protected function getIgnored(): array
{
return [
'App\Services\DynamicClassLoader', // Dynamically loaded
'Vendor\Package\AbstractBase', // External, no control
];
}
Use shouldAnalyzeFile() to skip specific files (e.g., migrations, config):
protected function shouldAnalyzeFile(string $file): bool
{
return !Str::contains($file, ['migrations/', 'config/']);
}
Analyzer checks @property, @method, and @var annotations. Example:
/**
* @property-read App\Models\User $user
*/
class UserResource extends JsonResource
{
// ...
}
If App\Models\User doesn’t exist, the test fails.
Add to .github/workflows/ci.yml:
- name: Run Analyzer
run: ./vendor/bin/phpunit --extension GrahamCampbell\Analyzer\PHPUnit\AnalyzerExtension
Run locally with:
./vendor/bin/phpunit --filter=ClassReferenceTest
False Positives with Namespaces:
use App; is used (without full namespace), Analyzer will fail. Solution: Use full paths (e.g., use App\Models\User).getIgnored() if unavoidable.Dynamic Class Loading:
eval(), class_alias(), or runtime namespace resolution (e.g., App\) will trigger failures. Solution: Exclude them in getIgnored().PHPDoc Parsing Quirks:
@mixin, custom tags) may cause parsing errors. Solution: Simplify PHPDoc or report issues to the package maintainer.Performance with Large Codebases:
getPaths().--parallel flag).PHPUnit Version Mismatch:
phpunit.xml or downgrade Analyzer.phpunit.xml:
<phpunit>
<extensions>
<extension class="GrahamCampbell\Analyzer\PHPUnit\AnalyzerExtension">
<arguments>
<argument name="debug" value="true"/>
</arguments>
</extension>
</extensions>
</phpunit>
getErrors() in tests:
public function testClassReferences()
{
$errors = $this->getErrors();
$this->assertEmpty($errors, implode("\n", $errors));
}
Custom Analyzers:
Extend GrahamCampbell\Analyzer\Analyzer to add logic (e.g., validate interfaces):
class CustomAnalyzer extends Analyzer
{
protected function analyzeNode(Node $node): void
{
if ($node instanceof Class_) {
$this->validateInterface($node);
}
}
}
Hook into PHPUnit Events:
Listen for TestListener events to integrate with other tools:
use GrahamCampbell\Analyzer\Events\AnalysisFailed;
AnalysisFailed::listen(function (AnalysisFailed $event) {
// Log to Slack/Teams
});
Override Node Processing:
Customize provideFilesToCheck() to analyze non-PHP files (e.g., Blade templates with @class directives):
protected function provideFilesToCheck(): array
{
return array_merge(
$this->getPaths(),
[resource_path('views/*.blade.php')]
);
}
use statements match the actual file case.composer.json.composer require --dev laravel-zero/framework
echo 'php artisan analyzer' >> .git/hooks/pre-commit
./vendor/bin/phpunit --extension GrahamCampbell\Analyzer\PHPUnit\AnalyzerExtension || exit 1
How can I help you explore Laravel packages today?