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

Dead Code Detector Laravel Package

shipmonk/dead-code-detector

PHPStan extension that detects and helps remove unused PHP code. Finds dead methods/properties/constants/enum cases, dead cycles and transitive dead members, even dead tested code. Supports popular frameworks like Symfony and is configurable via usage providers.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package** in your Laravel project:
   ```bash
   composer require --dev shipmonk/dead-code-detector
  1. Enable the extension in phpstan.neon:
    includes:
        - vendor/shipmonk/dead-code-detector/rules.neon
    
  2. Run PHPStan on your full codebase (including tests):
    vendor/bin/phpstan analyse --level 8
    

First Use Case

Run a one-time cleanup to identify dead code in your Laravel application:

vendor/bin/phpstan analyse --error-format removeDeadCode

This will:

  • Detect unused methods, properties, constants, and enum cases
  • Highlight dead code in routes, controllers, services, and tests
  • Show transitive dependencies (e.g., if a method calls another dead method)
  • New in 1.1.3: Fixed false positives for array_column() string keys (now correctly treated as property reads)

Implementation Patterns

Daily Workflow

  1. Pre-commit Check Add a Git hook or CI step to run:

    vendor/bin/phpstan analyse --error-format=json > dead-code-report.json
    
    • Use json output for programmatic checks (e.g., fail builds if dead code exists).
  2. Laravel-Specific Patterns

    • Routes: Dead code in routes/web.php (e.g., unused Route::get('/old', [Controller::class, 'deadMethod'])).
    • Controllers: Unused methods in App\Http\Controllers\*.
    • Services: Dead methods in App\Services\* called only via dependency injection.
    • Eloquent: Unused model methods, observers, or query scopes.
    • Symfony Event Listeners: Dead constructors in #[AsEventListener] classes (properly detected).
  3. Test Integration Enable test usage exclusion to avoid false positives:

    parameters:
        shipmonkDeadCode:
            usageExcluders:
                tests:
                    enabled: true
                    devPaths:
                        - %currentWorkingDirectory%/tests
    
    • Now, methods used only in tests will be flagged but not removed automatically.
  4. Automated Cleanup Schedule a weekly cleanup in CI:

    vendor/bin/phpstan analyse --error-format=removeDeadCode --generate-report=dead-code.md
    
    • Review dead-code.md before merging.

Integration Tips

  • Laravel Service Providers: Dead code in register()/boot() methods (e.g., unused bindings or listeners).

    // Example: Unused binding
    $this->app->bind('unused.service', UnusedService::class); // Flagged if never resolved
    
  • Eloquent Observers: Dead observed() methods or unused event handlers.

    // Example: Unused observer
    User::observe(UnusedObserver::class); // Flagged if `handle*` methods are dead
    
  • Console Commands: Unused methods in handle() or dead command classes.

    php artisan list // Cross-check with dead-code report
    
  • Middleware: Dead handle() methods in App\Http\Middleware\*.

  • Policies/Gates: Unused before()/after() methods or dead policy classes.

  • Symfony Event Listeners: Dead constructors in classes annotated with #[AsEventListener] are now properly detected as used.

  • array_column() Fix: No longer flags array_column($array, 'dynamicKey') as dead property access when 'dynamicKey' is a string literal. This resolves false positives in collection processing.


Gotchas and Tips

Pitfalls

  1. False Positives in Dynamic Code

    • Issue: Methods called via call_user_func() or ReflectionMethod may still be flagged as dead.
    • Fix: Extend ReflectionBasedMemberUsageProvider to whitelist dynamic calls:
      class DynamicCallProvider extends ReflectionBasedMemberUsageProvider {
          public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData {
              if ($method->getName() === 'dynamicMethod') {
                  return VirtualUsageData::withNote('Called dynamically');
              }
              return null;
          }
      }
      
  2. Test Exclusion Overkill

    • Issue: Enabling tests excluder may hide intentional test-only usages (e.g., private methods tested via reflection).
    • Fix: Use MemberUsageExcluder to fine-tune exclusions:
      class ReflectionExcluder implements MemberUsageExcluder {
          public function shouldExclude(ClassMemberUsage $usage, Node $node, Scope $scope): bool {
              return $scope->getFile()->getRelativeFilePath()->contains('tests/ReflectionTest.php');
          }
      }
      
  3. Transitive Dead Code Overload

    • Issue: Long chains of transitively dead methods clutter output.
    • Fix: Disable detailed reporting:
      parameters:
          shipmonkDeadCode:
              reportTransitivelyDeadMethodAsSeparateError: false
      
  4. Laravel Facades

    • Issue: Facade methods (e.g., Route::get()) may not be detected if called via dynamic strings.
    • Fix: Ensure phpstan/phpstan-symfony is installed and configured:
      includes:
          - vendor/phpstan/phpstan-symfony/extension.neon
      
  5. Enum Cases

    • Issue: Backed enums with tryFrom() may miss usages.
    • Fix: Explicitly enable enum detection:
      parameters:
          shipmonkDeadCode:
              detect:
                  deadEnumCases: true
      
  6. array_column() String Keys

    • Fixed in 1.1.3: No longer incorrectly flags array_column($array, 'key') as dead property access. This resolves false positives in collection processing (e.g., array_column($users, 'email')).

Debugging Tips

  1. Inspect False Positives Use --debug to see why a method was marked dead:

    vendor/bin/phpstan analyse --debug --error-format=removeDeadCode
    
  2. Isolate Analysis Run PHPStan on a single file to debug:

    vendor/bin/phpstan analyse src/Http/Controllers/UserController.php
    
  3. Check Usage Providers Verify enabled providers in phpstan.neon:

    parameters:
        shipmonkDeadCode:
            usageProviders:
                laravel:
                    enabled: true
                symfony:
                    enabled: true
    
  4. IDE Integration Configure editorUrl in phpstan.neon for clickable links:

    output:
        editorUrl: 'vscode://file/{file}:{line}'
    

Extension Points

  1. Custom Usage Providers Add support for Laravel-specific magic (e.g., app()->make() calls):

    class LaravelContainerProvider implements MemberUsageProvider {
        public function getUsages(Node $node, Scope $scope): array {
            if ($node instanceof MethodCall && $node->var->var->name === 'app') {
                $service = $scope->getType($node->getArgs()[0]->value)->getClassName();
                return [new ClassMethodUsage(..., new ClassMethodRef($service, '__construct'))];
            }
            return [];
        }
    }
    
  2. Excluders for Legacy Code Whitelist legacy methods called via call_user_func_array():

    class LegacyCallExcluder implements MemberUsageExcluder {
        public function shouldExclude(ClassMemberUsage $usage, Node $node, Scope $scope): bool {
            return $node instanceof MethodCall &&
                   $node->name->toString() === 'call_user_func_array' &&
                   $usage->getMemberRef()->getClassName() === 'LegacyService';
        }
    }
    
  3. Post-Processing Scripts Use the removeDeadCode output to generate a PR template:

    vendor/bin/phpstan analyse --error-format=removeDeadCode | grep "• Removed" > DEAD_CODE_REMOVALS.md
    

Laravel-Specific Quirks

  1. Route Cache

    • Clear route cache after removing dead route methods:
      php artisan route:clear
      
  2. Service Container

    • Dead bindings in AppServiceProvider may not be detected if resolved via app()->bind() without a class.
    • Workaround: Use app()->singleton() or explicit bind() with a class.
  3. Artisan Commands

    • Dead commands may still appear in php artisan list until cached.
    • Fix: Run composer dump-autoload after cleanup
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope