Weave Code
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

Analyzer Laravel Package

graham-campbell/analyzer

Analyzer is a PHP test utility by Graham Campbell that checks your code for references to classes that don’t actually exist. Compatible with PHP 8.1–8.5 and PHPUnit 10–13, helping catch broken imports and missing dependencies early.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install via Composer (dev dependency):
    composer require --dev graham-campbell/analyzer:^5.1
    
  2. Create a PHPUnit test class extending GrahamCampbell\Analyzer\AnalyzerTestCase:
    use GrahamCampbell\Analyzer\AnalyzerTestCase;
    
    class ClassReferenceTest extends AnalyzerTestCase
    {
        protected function getPaths(): array
        {
            return [__DIR__.'/../../app', __DIR__.'/../../config'];
        }
    }
    
  3. Run the test:
    ./vendor/bin/phpunit --testsuite ClassReferenceTest
    
    • Fails if any referenced classes (in code or PHPDoc) don’t exist.

First Use Case: CI Pre-Merge Gate

Add the test to your phpunit.xml:

<testsuites>
    <testsuite name="Analyzer">
        <directory>./tests/</directory>
        <file>./tests/ClassReferenceTest.php</file>
    </testsuite>
</testsuites>

Configure GitHub Actions to run it on PRs:

- name: Run Analyzer
  run: ./vendor/bin/phpunit --testsuite Analyzer

Implementation Patterns

Core Workflow: Extending AnalyzerTestCase

  1. Define paths to scan (override getPaths()):
    protected function getPaths(): array
    {
        return [
            app_path(),
            database_path(),
            config_path(),
        ];
    }
    
  2. Ignore false positives (override getIgnored()):
    protected function getIgnored(): array
    {
        return [
            'App\Services\Legacy\*', // Ignore legacy classes
            'App\Contracts\*',      // Ignore interfaces/contracts
        ];
    }
    
  3. Filter files dynamically (override shouldAnalyzeFile()):
    protected function shouldAnalyzeFile(string $file): bool
    {
        return !str_contains($file, 'vendor/') && parent::shouldAnalyzeFile($file);
    }
    
  4. Custom file sources (override provideFilesToCheck()):
    protected function provideFilesToCheck(): array
    {
        return [
            __DIR__.'/../app/Providers/AppServiceProvider.php',
            __DIR__.'/../app/Http/Controllers/ApiController.php',
        ];
    }
    

Integration with Laravel

  • Service Providers: Validate register()/boot() methods for missing classes:
    // tests/ClassReferenceTest.php
    protected function getPaths(): array
    {
        return [app_path('Providers')];
    }
    
  • API Resources: Ensure PHPDoc matches runtime behavior:
    // app/Http/Resources/UserResource.php
    /**
     * @property-read App\Models\User $user
     */
    
  • Event Listeners: Catch invalid event class references:
    // tests/ClassReferenceTest.php
    protected function getIgnored(): array
    {
        return ['App\Events\*']; // Exclude events (handled separately)
    }
    

Advanced: Custom Analyzer Logic

Extend GrahamCampbell\Analyzer\Analyzer for bespoke rules:

use GrahamCampbell\Analyzer\Analyzer;

class CustomAnalyzer extends Analyzer
{
    protected function analyzeClass(string $class): void
    {
        if (str_contains($class, 'App\Services\Payment')) {
            $this->assertClassExists($class, 'Payment service classes must exist.');
        }
        parent::analyzeClass($class);
    }
}

Use it in tests:

use GrahamCampbell\Analyzer\AnalyzerTestCase;

class CustomClassReferenceTest extends AnalyzerTestCase
{
    protected function createAnalyzer(): Analyzer
    {
        return new CustomAnalyzer();
    }
}

Gotchas and Tips

Pitfalls

  1. False Positives with PHPDoc:

    • Issue: PHPDoc references (e.g., @property App\User $user) may fail if the class exists but the property doesn’t.
    • Fix: Use getIgnored() to exclude PHPDoc-only checks:
      protected function getIgnored(): array
      {
          return ['@property', '@method', '@var'];
      }
      
    • Alternative: Use shouldAnalyzeFile() to skip files with known PHPDoc issues.
  2. Namespace Aliases:

    • Issue: use App; aliases are not resolved by default.
    • Fix: Pre-process files to expand aliases or exclude them:
      protected function shouldAnalyzeFile(string $file): bool
      {
          return !str_contains(file_get_contents($file), 'use App;');
      }
      
  3. Dynamic Class Loading:

    • Issue: Classes loaded via class_alias() or eval() will fail.
    • Fix: Exclude dynamic files or mock them in tests:
      protected function getIgnored(): array
      {
          return ['App\Dynamic\*'];
      }
      
  4. Performance in Large Codebases:

    • Issue: Scanning app/ + vendor/ can be slow.
    • Fix: Limit paths and use provideFilesToCheck() for targeted scans:
      protected function provideFilesToCheck(): array
      {
          return glob(app_path('*/*.php'));
      }
      
  5. PHPUnit Version Mismatches:

    • Issue: Analyzer v5+ requires PHPUnit 10–13.
    • Fix: Pin PHPUnit in composer.json:
      "require-dev": {
          "phpunit/phpunit": "^10.0"
      }
      

Debugging Tips

  • Verbose Output: Enable debug mode in AnalyzerTestCase:
    protected function setUp(): void
    {
        $this->analyzer->setDebug(true);
    }
    
  • Isolate Failures: Run tests on a single file:
    ./vendor/bin/phpunit --filter ClassReferenceTest --testdox-html
    
  • Check Ignored Classes: Verify getIgnored() isn’t hiding real issues:
    public function testIgnoredClasses()
    {
        $this->assertArrayNotContainsString('App\Nonexistent', $this->getIgnored());
    }
    

Extension Points

  1. Custom Class Resolver: Override getClassResolver() to use a custom resolver (e.g., for Laravel’s autoloader):

    protected function getClassResolver(): \phpDocumentor\Reflection\Types\Resolver\ClassString
    {
        return new CustomClassResolver();
    }
    
  2. Pre-Analysis Hooks: Modify analyzeFile() to skip specific patterns:

    protected function analyzeFile(string $file): void
    {
        if (str_contains($file, 'migrations/')) {
            return; // Skip migrations
        }
        parent::analyzeFile($file);
    }
    
  3. Post-Analysis Actions: Extend afterAnalysis() to log or notify:

    protected function afterAnalysis(): void
    {
        if ($this->hasErrors()) {
            $this->logErrorsToSlack();
        }
    }
    

Laravel-Specific Quirks

  • Facade Classes: Facades (e.g., Cache::class) will fail unless their underlying classes exist. Fix: Ignore facades or validate their bindings:
    protected function getIgnored(): array
    {
        return ['Illuminate\Support\Facades\*'];
    }
    
  • Service Container: Classes bound to the container (e.g., App\Services\PaymentService) must exist at runtime. Fix: Run Analyzer after bootstrap/app.php:
    $this->analyzer->setPaths([app_path()]);
    $this->analyzer->analyze();
    
  • Laravel Mix: Exclude resources/js or resources/css if they contain PHP:
    protected function shouldAnalyzeFile(string $file): bool
    {
        return !str_starts_with($file, resource_path('js/'));
    }
    

Performance Optimization

  • Cache Results: Use PHPUnit’s --cache-result flag to avoid re-scanning:
    ./vendor/bin/phpunit --testsuite Analyzer --cache-result
    
  • Parallel Testing: Run Analyzer in parallel with other tests:
    <!-- phpunit.xml -->
    <phpunit parallel="true">
        <testsuites>
            <testsuite name="Analyzer">
                <file>tests/ClassReferenceTest.php</file>
            </testsuite>
        </testsuites>
    </phpunit>
    
  • Exclude Vendors: Always exclude vendor/ to speed up scans:
    protected function shouldAnalyzeFile(string $file): bool
    {
        return !str_contains($file, 'vendor/');
    }
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport