dead code detector
0.7.0
PHPStan extension to find unused PHP code in your project with ease!
composer require --dev shipmonk/dead-code-detectorUse official extension-installer or just load the rules:
# phpstan.neon.dist
includes:
- vendor/shipmonk/dead-code-detector/rules.neonphpstan/phpstan-symfony with containerXmlPath must be used#[AsEventListener] attribute#[AsController] attribute#[AsCommand] attribute#[Required] attribute#[Route] attributesEventSubscriberInterface::getSubscribedEventsonKernelResponse, onKernelRequest, etc#[AsEntityListener] attributeDoctrineORMEvents::* eventsDoctrineCommonEventSubscriber methods#[PreFlush], #[PostLoad], ...testXxx methods@test, @before, @afterClass etc#[Test], #[Before], #[AfterClass] etchandleXxx, renderXxx, actionXxx, injectXxx, createComponentXxxSmartObject magic calls for @property annotationsAll those libraries are autoenabled when found within your composer dependencies. If you want to force enable/disable some of them, you can:
# phpstan.neon.dist
parameters:
shipmonkDeadCode:
usageProviders:
phpunit:
enabled: trueReflectionClass is detected as used
$reflection->getConstructor(), $reflection->getConstant('NAME'), $reflection->getMethods(), ...vendor is not reported as dead
PsrLogLoggerInterface::log is automatically considered usedThose providers are enabled by default, but you can disable them if needed.
shipmonk.deadCode.memberUsageProvider and implement ShipMonkPHPStanDeadCodeProviderMemberUsageProvider# phpstan.neon.dist
services:
-
class: AppApiOutputUsageProvider
tags:
- shipmonk.deadCode.memberUsageProviderImportant
The interface & tag changed in 0.7. If you are using PHPStan 1.x, those were used differently.
ShipMonkPHPStanDeadCodeProviderReflectionBasedMemberUsageProvider:use ReflectionMethod;
use ShipMonkPHPStanDeadCodeProviderReflectionBasedMemberUsageProvider;
class ApiOutputUsageProvider extends ReflectionBasedMemberUsageProvider
{
public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
{
// all methods from our ApiOutput interface are called automatically (e.g. during serialization)
return $method->getDeclaringClass()->implementsInterface(ApiOutput::class);
}
}MemberUsageProvider interface:use ReflectionMethod;
use ShipMonkPHPStanDeadCodeGraphClassMethodRef;
use ShipMonkPHPStanDeadCodeGraphClassMethodUsage;
use ShipMonkPHPStanDeadCodeProviderMemberUsageProvider;
use SymfonyComponentSerializerSerializerInterface;
class DeserializationUsageProvider implements MemberUsageProvider
{
/**
* @return list<ClassMemberUsage>
*/
public function getUsages(Node $node, Scope $scope): array
{
if (!$node instanceof MethodCall) {
return [];
}
if (
// our deserialization calls constructor
$scope->getType($node->var)->getObjectClassNames() === [SerializerInterface::class] &&
$node->name->toString() === 'deserialize'
) {
$secondArgument = $node->getArgs()[1]->value;
$serializedClass = $scope->getType($secondArgument)->getConstantStrings()[0];
// record the method it was called from (needed for proper transitive dead code elimination)
$originRef = $this->getOriginMethodRef($scope);
// record the hidden constructor call
$constructorRef = new ClassMethodRef($serializedClass->getValue(), '__construct', false);
return [new ClassMethodUsage($originRef, $constructorRef)];
}
return [];
}
private function getOriginMethodRef(Scope $scope): ?ClassMethodRef
{
return new ClassMethodRef(
$scope->getClassReflection()->getName(),
$scope->getFunction()->getName(),
false,
);
}
} ------ ------------------------------------------------------------------------
Line src/App/Facade/UserFacade.php
------ ------------------------------------------------------------------------
26 Unused AppFacadeUserFacade::updateUserAddress
? shipmonk.deadMethod
Thus AppEntityUser::updateAddress is transitively also unused
Thus AppEntityAddress::setPostalCode is transitively also unused
Thus AppEntityAddress::setCountry is transitively also unused
Thus AppEntityAddress::setStreet is transitively also unused
Thus AppEntityAddress::MAX_STREET_CHARS is transitively also unused
------ ------------------------------------------------------------------------
phpstan.neon.dist:parameters:
shipmonkDeadCode:
reportTransitivelyDeadMethodAsSeparateError: trueremoveDeadCode error format:vendor/bin/phpstan analyse --error-format removeDeadCodeclass UserFacade
{
- public const TRANSITIVELY_DEAD = 1;
-
- public function deadMethod(): void
- {
- echo self::TRANSITIVELY_DEAD;
- }
}$unknown->method()) by marking all methods named method as used
new $unknown() will mark all constructors as usedphpstan.neon.dist:$unknown::CONSTANT)parameters:
shipmonkDeadCode:
trackMixedAccess: false-vvv and you will see some diagnostics:Found 2 usages over unknown type:
• setCountry method, for example in AppEntityUser::updateAddress
• setStreet method, for example in AppEntityUser::updateAddress
__get, __set etc) are never reported as dead
__construct, __cloneparameters:
ignoreErrors:
- '#^Unused .*?::__construct$#'MemberUsageProvider:use ShipMonkPHPStanDeadCodeProviderReflectionBasedMemberUsageProvider;
class IgnoreDeadInterfaceUsageProvider extends ReflectionBasedMemberUsageProvider
{
public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
{
return $method->getDeclaringClass()->isInterface();
}
}composer checkcomposer fix:cs