Can PHPStan find Dead Public Methods?

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()

  • speedUp
  • slowDown

2. Collect all Method Calls


  • speedUp

3. Subtract First List from Second

There we have a list of unused public methods:

  • slowDown

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() { ... }


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:

  • collect one group of data
  • collect another group of data
  • compare both groups and show the result

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:

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


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:

  • public methods only
  • skip Trait_, Enum and Interface methods
  • skip methods overriding parent class or required by interface
  • skip methods that have @api annotation, or the class has @api annotation
  • skip methods in PHPUnit test case
  • skip methods with an attribute

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
        methods: true
        properties: true
        constants: true

Run PHPStan and see the results.

Good luck, tidy up and have fun!

Happy coding!

Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!