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.
array_column() string keys as property reads (#362)#[AsEventListener] host class as used (#356)view(), View::make()/first(), response()->view(), ->with() as usedView::composer()/View::creator(), including 'Class@method' syntaxdata_class mapping (#334)
get*/set*/is*/has*/can*/add*/remove*), public properties, and __construct on data classes as useddata_class from OptionsResolver, AbstractController::createForm(), FormFactoryInterface::create() and related methods(array) cast, get_object_vars(), get_mangled_object_vars(), json_encode(), serialize()JsonSerializable, __serialize(), Serializable#[AutowireIterator]/#[AutowireLocator] via DIC XML (#336)
'app.handler') are now mapped to concrete classes, fixing false positives on defaultIndexMethod/defaultPriorityMethodshipmonkDeadCode.usageProviders.symfony.containerXmlPaths must be set up#[AsTwigComponent(template: new FromMethod(...))] (#341)#[MapInput] in console commands (#340)#[PostRemove] lifecycle callback detection (#337)ToolEvents to listener heuristic (#342)[@dataProvider](https://github.com/dataProvider) annotation recognition (#338)Route::get(), post(), put(), patch(), delete(), any(), match(), resource(), apiResource()
[Controller::class, 'method']'Controller@method'Controller::classEvent::listen(), Event::subscribe()handle* methods whose first parameter has a class typeSchedule::job()Gate::define() (callable + invokable), Gate::policy()$this->authorize() calls resolve policy class via Laravel's namespace convention (Models\ → Policies\)__construct, handle on Illuminate\Console\Command subclasses__construct, handle, failed, middleware, retryUntil, uniqueId, tags, backoff, uniqueVia, displayName on ShouldQueue / Dispatchable classes__construct, register, boothandle, terminate, __construct (detected by first parameter type Illuminate\Http\Request)via, toMail, toArray, toDatabase, toBroadcast, toVonage, toSlackauthorize, rules, messages, attributes, prepareForValidation, passedValidation, failedValidation, failedAuthorizationbefore, viewAny, view, create, update, delete, restore, forceDelete (heuristic via \Policies\ namespace + Policy suffix)build, content, envelope, attachments, headersbroadcastWith, broadcastAs, broadcastWhenpaginationInformationrouteNotificationFor* methodsboot, booted, casts, newFactoryscope* methodsIlluminate\Database\Eloquent\Relations\*Illuminate\Database\Eloquent\Casts\Attributeget*Attribute() / set*Attribute()Model::observe() calls + #[ObservedBy] attribute (all observer event methods)definition, configurerunup, down#[Autoconfigure(constructor:, calls:)], #[AutowireCallable], #[TaggedIterator], #[TaggedLocator], #[Required] on properties, #[AsSchedule], #[AsCronTask], #[AsPeriodicTask], !php/enum references in config yamls (#317)containerXmlPaths configuration - no need for phpstan/phpstan-doctrine for DIC calls analysis (#325)#[AsTwigComponent]/#[AsLiveComponent] (constructor, mount()), #[LiveProp], #[LiveAction], #[LiveListener], lifecycle hooks (#329)new ReflectionMethod(Class, 'method') (#313)PHP ^8.1 (was ^7.4) (#296)phpstan/phpstan ^2.1.41, (was ^2.1.23) (#297)$controller->render('my.twig', ['param' => $viewModel]),#[Template] controller methodsTwig\Environment::render() and similartest* methods, setUp/tearDown, [@dataProvider](https://github.com/dataProvider) (#299)ApiPhpDocUsageProvider: only count phpdocs within analysed files (#284)parameters:
shipmonkDeadCode:
detect:
deadEnumCases: false
deadProperties:
neverRead: false
neverWritten: false
stream_wrapper_register magic (#266)usageProviders.symfony.configDirusageExcluders.tests.devPathsphpstan/phpstan now requires ^2.1.23 (was ^2.1.12) (#254)#[AsDoctrineListener] #[AutoconfigureTag('doctrine.event_listener')] #[AsTransitionListenerWorkflow] and other Event listener attributes (#248, by @S1ructure)#[AsMessageHandler] attribute (#247, by @S1ructure)[@api](https://github.com/api) over enums and enum cases (#240)ReflectionClass<object>::getMethods() etc. (#241)parameters:
shipmonkDeadCode:
detect:
deadEnumCases: true
[@final](https://github.com/final) class is inherited (#233)ClassMemberRef is now generic, you may need to update phpdocs of thoseClassConstantRef constructor now requires TrinaryLogic $isEnumCase argumentphpstan/phpstan now requires ^2.1.12 (was ^2.1.9)usageProviders.symfony.configDir wiring (#216)#[DataProviderExternal] (#212)[@api](https://github.com/api) phpdoc for marking public API of libraries or tools (#200)
phpstan/phpstan-src itself :tada:devPaths config for tests excluder (#202)composer.json for tests excluder (#203)$foo->$unknown() (#189)
$foo as used (including possible descendants, ancestors, traits, ...)ReflectionClass->getMethods() now mark alive also methods of descendants (#189)ReflectionClass<object> (#193)
$anyObjectReflection->getMethod('foo') marks all foo methods of all types as aliveusageOverMixed excluder#[AsTwigFilter], #[AsTwigFunction], #[AsTwigTest] and new TwigFilter(..., callback) counterparts (#194, @zacharylund)- #[Deprecated] // [@phpstan-ignore](https://github.com/phpstan-ignore) shipmonk.deadMethod
- public function someMethod(): void
+ #[Deprecated]
+ public function someMethod(): void // [@phpstan-ignore](https://github.com/phpstan-ignore) shipmonk.deadMethod
#[Assert\Callback] (#180, @zacharylund)repositoryMethod in #[UniqueEntity] (#184) • Removed method UserFacade::deadMethod
! Excluded usage at tests/User/UserFacadeTest.php:241 left intact
phpstan/phpstan now requires ^2.1.9 (was ^2.1.7) (#169)parameters:
shipmonkDeadCode:
debug:
usagesOf:
- App\User\Entity\Address::__construct
with -vvv outputs e.g.:
App\User\Entity\Address::__construct
|
| Marked as alive by:
| entry virtual usage from ShipMonk\PHPStan\DeadCode\Provider\SymfonyUsageProvider (Route method via #[Route] attribute)
| calls App\User\RegisterUserController::__invoke:36
| calls App\User\UserFacade::registerUser:142
| calls App\User\Entity\Address::__construct
|
| Found 2 usages:
| • src/User/UserFacade.php:142
| • tests/User/Entity/AddressTest.php:64 - excluded by tests excluder
new Foo->method() does not mark method as used on Foo children anymoredefaultIndexMethod on AutowireLocator/AutowireIterator attribute (#161, @ruudk)phpstan/phpstan ^2.1.7 (was ^2.0.0) (#151)ClassMemberUsage now requires UsageOrigin, not optionally ClassMethodRef (#165)UsageOriginDetector was removed (#165)
UsageOrigin::createRegular or UsageOrigin::createVirtualReflectionBasedMemberUsageProvider now require note (#171)
true => VirtualUsageData::withNote('why considered used')false => nulltrackMixedAccess was replaced with excluder (#167)parameters:
shipmonkDeadCode:
- trackMixedAccess: false
+ usageExcluders:
+ usageOverMixed:
+ enabled: true
parameters:
shipmonkDeadCode:
usageExcluders:
tests:
enabled: true # off by default
MemberUsageExcluder interface!php const Foo\Bar::BAZ (#131)factory: [Acme\SerializerFactory, create] (#128)$unknown::CONST)--error-format removeDeadCode)PHP 8.2)constant('PDO::ATTR_ERRMODE'))!php/const Foo::BAR usages from your yamls)MemberUsageProvider$reflectionClass->getMethod('foo') marks foo method as usedEventSubscriberInterface::getSubscribedEvents (#122)Doctrine\Common\EventSubscriber::getSubscribedEvents (#127)# classes & methods
- `ShipMonk\PHPStan\DeadCode\Provider\MethodEntrypointProvider::getEntrypoints(): list<ReflectionMethod>`
+ `ShipMonk\PHPStan\DeadCode\Provider\MemberUsageProvider::getUsages(): list<ClassMemberUsage>`
- `ShipMonk\PHPStan\DeadCode\Provider\SimpleMethodEntrypointProvider::isEntrypointMethod(): bool`
+ `ShipMonk\PHPStan\DeadCode\Provider\ReflectionBasedMemberUsageProvider::shouldMarkMethodAsUsed(): bool`
# neon config params
- `parameters.shipmonkDeadCode.entrypoints`
+ `parameters.shipmonkDeadCode.usageProviders`
- `parameters.shipmonkDeadCode.trackCallsOnMixed`
+ `parameters.shipmonkDeadCode.trackMixedAccess`
# neon service tag
- `shipmonk.deadCode.entrypointProvider`
+ `shipmonk.deadCode.memberUsageProvider`
phpstan/phpstan 2.0trackMixedCalls: false (#109)vendor/bin/phpstan analyse --error-format removeDeadCode$unknownClass->method()phpstan/phpstan 1.11.7 (was 1.11.0) (#107) ------ ------------------------------------------------------------------------
Line src/App/Facade/UserFacade.php
------ ------------------------------------------------------------------------
26 Unused App\Facade\UserFacade::updateUserAddress
🪪 shipmonk.deadMethod
💡 Thus App\Entity\User::updateAddress is transitively also unused
💡 Thus App\Entity\Address::setPostalCode is transitively also unused
💡 Thus App\Entity\Address::setCountry is transitively also unused
💡 Thus App\Entity\Address::setStreet is transitively also unused
#[AsCommand], #[AsController], Bundle children (#89)EntrypointProvider interface changed (#74)
SimpleMethodEntrypointProvider for easy transition__clone methods (#80)nette provider in config (#81)How can I help you explore Laravel packages today?