Can PHPStan find Dead Public Methods?

Found a typo? Edit me
This post was updated at December 2022 with fresh know-how.
What is new?

Update to new package with simple PHPStan configuration.


This bold question has been around PHP Internet for many years... at least since 2008. In 2017 I added dead public method sniff to Symplify Coding Standard.

It runs on bare PHP tokens without any type or AST, so the capability was limited. Yet it was able to detect a few unused methods. Now, 5 years later, we maybe have a better solution.

The sniff rule looked for unused public method with very basic algorithm:

1. Collect all Public Methods

public function speedUp()
{
}

public function slowDown()
{
}


2. Collect all Method Calls

$someObject->speedUp();


3. Subtract First List from Second

There we have a list of unused public methods:


That's it!


This sniff helped to detect many dead methods.


But without types, it misses essential information like this:

class Car
{
    public function speedUp() { ... }
}

vs

class Bus
{
    public function speedUp() { ... }
}


The same-named methods are in 2 different classes. Even if only one is used, both are reported as used. That's not good enough, so we've removed the token-based solution.


Turning Tide

A week ago PHPStan 1.8 introduced similar feature, called collectors. The principle is simple:


I got excited and wanted to try this feature as soon as I had some free time.

Last week I made the first rule that detects unused public constants:

I'm roughly playing with new collectors from PHPStan 1.8 😇

This is how "unused public constant" is found ↓https://t.co/SkTCITcLh4 pic.twitter.com/zt4yyq0Hmh

— Tomas Votruba 🇺🇦 (@VotrubaT) June 30, 2022


Detecting constant call is relatively easy because there is always one exact class and constant name:

SomeClass::SOME_CONSTANT


We've added rules to a few projects, and the results are fantastic.

How about trying the same approach to public methods?


PHPStan Collector for Unused Method

With method calls, this is a bit complex, as caller type could be anything:

$value->speedUp(); // $value is mixed type

function run(?Car $value) {
    $value->speedUp(); // $value is null|Car
}

/** @var Car $value */
$value->speedUp(); // $value is probably Car


I wanted to see how many false positives this rule will catch and try out collectors. Take this as a joyful learning experience.


To keep the rule simple and reliable, we've narrowed the scope further down:


#[Required]
public function autowire(...)
{
    // ...
}


Are you maintaining an open-source project with a method that is designed for external use?

Mark it with @api to make the rule skip it.

3. Steps to run it on Your Project

  1. Install the TomasVotruba/unused-public package
composer require tomasvotruba/unused-public --dev

The package is available on PHP 7.2+, as downgraded.


  1. With PHPStan extension installer, the rules are already installed.

To enable them, use parameters:

# phpstan.neon
parameters:
    unused_public:
        methods: true
        properties: true
        constants: true


Run PHPStan and see the results.


Good luck, tidy up and have fun!


Happy coding!


Have you find this post useful? Do you want more?

Follow me on Twitter, RSS or support me on GitHub Sponsors.