shipmonk/dead-code-detector
PHPStan extension that detects unused PHP code: dead methods, properties, constants, and enum cases. Finds dead cycles and transitive dead members, can flag dead tested code, supports popular frameworks (e.g., Symfony), and offers customizable usage providers with optional auto-removal.
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
Analyze your entire codebase (including tests):
vendor/bin/phpstan analyse --level=max
Ensure --level=max to catch all potential dead code.
First Use Case Review the output for unused methods, properties, or constants. Example:
Unused App\Service\UserService::legacyMethod
Pre-Commit Hook
Integrate with Git hooks to run phpstan before commits:
vendor/bin/phpstan analyse --error-format=github --generate-baseline
Use --generate-baseline to ignore new errors temporarily.
CI/CD Pipeline Add to your CI (e.g., GitHub Actions):
- name: Run Dead Code Detection
run: vendor/bin/phpstan analyse --error-format=checkstyle
Iterative Cleanup
phpstan to validate changes.--removeDeadCode for automated cleanup (caution: review changes first):
vendor/bin/phpstan analyse --error-format=removeDeadCode
Symfony/Laravel Projects
Ensure phpstan/phpstan-symfony is installed for framework-specific detection:
composer require --dev phpstan/phpstan-symfony
Configure container paths in phpstan.neon:
parameters:
shipmonkDeadCode:
usageProviders:
symfony:
containerXmlPaths:
- var/cache/dev/App_KernelDevDebugContainer.xml
Test-Only Usages Exclude test usages to avoid false positives:
parameters:
shipmonkDeadCode:
usageExcluders:
tests:
enabled: true
Custom Providers Extend detection for project-specific logic (e.g., custom attributes):
// src/UsageProviders/CustomAttributeProvider.php
class CustomAttributeProvider implements MemberUsageProvider {
public function getUsages(Node $node, Scope $scope): array {
if ($node instanceof Attribute && $node->name->toString() === '[UsedInMyFramework]') {
return [new ClassMethodUsage(..., new ClassMethodRef('App\Entity\User', 'customMethod'))];
}
return [];
}
}
Register in phpstan.neon:
services:
- App\UsageProviders\CustomAttributeProvider{tags: ['shipmonk.deadCode.memberUsageProvider']}
False Positives in Dynamic Code
call_user_func(), ReflectionMethod, or dynamic strings (e.g., $class::$method()) may be misclassified.ReflectionBasedMemberUsageProvider to whitelist dynamic calls:
class DynamicCallProvider extends ReflectionBasedMemberUsageProvider {
public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData {
return VirtualUsageData::withNote('Dynamic call detected');
}
}
Transitive Dead Code Overload
reportTransitivelyDeadMethodAsSeparateError floods output with cascading errors.false) unless debugging specific cycles:
parameters:
shipmonkDeadCode:
reportTransitivelyDeadMethodAsSeparateError: false
Test Excluder Misconfiguration
vendor/bin/phpstan analyse --error-format=github --debug
Automatic Removal Risks
--removeDeadCode may delete critical logic if misconfigured.git diff > dead-code-changes.diff
Isolate Issues
Use --focus-file to analyze specific files:
vendor/bin/phpstan analyse --focus-file=src/Service/UserService.php
Inspect Usage Origins Enable debug mode to trace usage sources:
parameters:
shipmonkDeadCode:
debug: true
Custom Error Formatting Generate IDE-friendly output (e.g., for PHPStorm):
vendor/bin/phpstan analyse --error-format=json --output=phpstan-dead-code.json
Override Default Detection
Disable specific member types (e.g., constants) in phpstan.neon:
parameters:
shipmonkDeadCode:
detect:
deadConstants: false
Whitelist Methods
Use MemberUsageExcluder to ignore known false positives:
class WhitelistExcluder implements MemberUsageExcluder {
public function shouldExclude(ClassMemberUsage $usage, Node $node, Scope $scope): bool {
return $usage->getClassMethodRef()->getMethodName() === 'getLegacyData';
}
}
Leverage PHPStan Levels
Run with --level=8 for stricter checks (but slower):
vendor/bin/phpstan analyse --level=8
Route/Event Listeners
Ensure phpstan/phpstan-symfony is configured for Laravel’s service container:
parameters:
symfony:
containerXmlPath: bootstrap/cache/services.php
Eloquent Observers/Factories
Dead code in boot() or define() methods may be missed if not analyzed transitively. Enable:
parameters:
shipmonkDeadCode:
detect:
deadMethods: true
Artisan Commands
Commands using #[Command] are detected, but ensure their classes are autoloaded in composer.json:
"autoload-dev": {
"psr-4": { "App\\Console\\": "app/Console/" }
}
How can I help you explore Laravel packages today?