shipmonk/dead-code-detector
PHPStan extension that finds unused PHP code: dead methods, properties, constants, enum cases, cycles, and transitive “dead-tested” members. Supports popular frameworks like Symfony and can auto-remove dead code. Configurable detection and usage providers.
Installation:
composer require --dev shipmonk/dead-code-detector
Add the extension to your phpstan.neon:
includes:
- vendor/shipmonk/dead-code-detector/rules.neon
First Run:
vendor/bin/phpstan analyse --level=max
Ensure you analyze the entire codebase (both src and tests) to detect all usages.
First Use Case:
Run the detector on a Laravel project to identify unused methods, constants, or properties in your App namespace. Example output:
Unused App\Services\UserService::legacyMethod
Pre-Commit Hook:
Integrate with Git hooks to run phpstan with --error-format=removeDeadCode before commits:
vendor/bin/phpstan analyse --error-format=removeDeadCode --level=max
Automatically removes dead code if no false positives are detected.
CI Pipeline: Add a step to run the detector in CI (e.g., GitHub Actions):
- name: Detect dead code
run: vendor/bin/phpstan analyse --level=max
Fail the build if dead code is found (adjust --error-format as needed).
Laravel-Specific Patterns:
Route::get('/old', [UserController::class, 'unusedMethod'])).Test-Driven Development:
Use the tests usage excluder to separate test-only usages from production code:
parameters:
shipmonkDeadCode:
usageExcluders:
tests:
enabled: true
devPaths:
- %currentWorkingDirectory%/tests
This ensures dead code in src used only in tests is reported but not auto-removed.
Custom Providers:
Extend ReflectionBasedMemberUsageProvider for framework-specific logic (e.g., Laravel’s #[AsTwigComponent] or #[LiveAction]):
class LaravelLiveComponentProvider extends ReflectionBasedMemberUsageProvider {
public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData {
if ($method->hasAttribute(LiveAction::class)) {
return VirtualUsageData::withNote('Used in LiveComponent');
}
return null;
}
}
Register in phpstan.neon:
services:
- class: App\LaravelLiveComponentProvider
tags: [shipmonk.deadCode.memberUsageProvider]
False Positives in Dynamic Code:
call_user_func() or reflection). Use custom MemberUsageProvider to handle these cases.shouldMarkMethodAsUsed to return VirtualUsageData for dynamically invoked methods.Transitive Dead Code:
parameters:
shipmonkDeadCode:
reportTransitivelyDeadMethodAsSeparateError: true
Test Excluder Misconfiguration:
tests excluder is enabled but paths are misconfigured, dead code used in tests might be incorrectly reported as "unused in production." Verify devPaths in composer.json or explicitly define them.Auto-Removal Risks:
--error-format=removeDeadCode will permanently delete dead code. Review changes carefully, especially in shared repositories.vendor/bin/phpstan analyse --error-format=removeDeadCode --generate-report=report.txt
Framework-Specific Quirks:
phpstan/phpstan-symfony is installed for DIC support. Configure containerXmlPath if using custom container paths.#[PreFlush] may not be detected if the event system isn’t fully analyzed. Run PHPStan with --level=max to include all rules.phpstan.neon for detection.Inspect Usages:
Use --generate-report=report.txt to see detailed usage origins. Example:
📍 Usage origin: src/Http/Controllers/UserController.php:42 (Route::get('/users', [UserController::class, 'index']))
Disable Specific Checks: Temporarily disable checks for troubleshooting:
parameters:
shipmonkDeadCode:
detect:
deadMethods: false
deadProperties: { neverRead: false }
Custom Excluders:
Implement MemberUsageExcluder to ignore specific usages (e.g., legacy code or third-party calls):
class LegacyCodeExcluder implements MemberUsageExcluder {
public function shouldExclude(ClassMemberUsage $usage, Node $node, Scope $scope): bool {
return str_contains($usage->getClassRef()->getClassName(), 'Legacy');
}
}
IDE Integration:
Configure editorUrl in phpstan.neon to open files directly in your IDE:
parameters:
editorUrl: 'phpstorm://open?file=%f&line=%l'
Custom Usage Providers:
MemberUsageProvider to parse AST nodes and emit usages.public function getUsages(Node $node, Scope $scope): array {
if ($node instanceof MethodCall && $scope->getType($node->var)->isInstanceOf(EventDispatcher::class)) {
return [new ClassMethodUsage($usageOrigin, new ClassMethodRef($node->name->toString()))];
}
return [];
}
Post-Processing:
Use PHPStan’s ErrorFormatter to customize output or integrate with tools like PHP-CS-Fixer:
use ShipMonk\PHPStan\DeadCode\ErrorFormatter\RemoveDeadCodeErrorFormatter;
$errorFormatter = new RemoveDeadCodeErrorFormatter();
$errorFormatter->processFile($file, $errors);
Performance:
vendor/) from analysis to speed up runs:
excludeFiles:
- vendor/**
- node_modules/**
--memory-limit=2G for large codebases.Laravel-Specific:
#[AutowireCallable] or #[AutowireLocator] attributes to detect dead methods in service containers.app/Http/Kernel.php and Route::middleware() calls.How can I help you explore Laravel packages today?