21

I've searched all over for something like this but I believe the word "use" is perhaps too common for any helpful results:

What is the easiest way to remove all un-used use statements from class files in a PHP codebase?

Edit: For the sake of simplicity, we can ignore detecting use statements that are used for annotations.

5
  • How would this tool know when a namespace isn't being used? Commented Mar 27, 2012 at 18:38
  • 1
    Well you need to collect every use-statement, parsing your files with tokenizer maybe and look if it's instanciated or called statically. But it would require a bit of work. Commented Mar 27, 2012 at 18:43
  • I've edited the code in my answer, you should try it again now :). I tested some cases, and it worked. I hope it's for your satisfaction now. It works only for one file though; for larger projects you should adjust it, and build a bigger index. Commented Mar 28, 2012 at 9:44
  • php storm: alt + control + o (it's a no brainer) Commented Jan 2, 2016 at 18:47
  • 1
    @ling You forgot step #1 where you pay $89/yr. Commented Jan 3, 2016 at 7:52

3 Answers 3

32

Check FriendsOfPHP's PHP-CS-Fixer https://github.com/FriendsOfPHP/PHP-CS-Fixer

Sign up to request clarification or add additional context in comments.

2 Comments

./php-cs-fixer fix folder/ --fixers=unused_use worked for me
./php-cs-fixer fix src/Backend/ --rules=no_unused_imports did the trick for me
8

EDIT

I have completely rewritten it, so now it is much more powerful:

  • Traits and anonymous/lambda functions are ignored
  • Now taking care of catch blocks, class extensions and interfaces
  • Indentation and comments don't matter
  • Multiple declarations for namespace aliases work too
  • Static and object class calls are recognized as "usage" ($u->getUsages())
  • Full and half qualified usages are not treated

The test file, class.php:

use My\Full\Classname as Another, My\Full\NSname, Some\Other\Space;

/* some insane commentary */ use My\Full\NSname1; use ArrayObject;

$obj = new namespaced\Another;
$obj = new Another;

$a = new ArrayObject(array(1));

Space::call();

$a = function($a, $b, $c = 'test') use ($obj) {
  /* use */
};

class MyHelloWorld extends Base {
  use traits, hello, world;
}

And here the script:

class UseStatementSanitizer
{
  protected $content;

  public function __construct($file)
  {
    $this->content = token_get_all(file_get_contents($file));

    // we don't need and want them while parsing
    $this->removeTokens(T_COMMENT);
    $this->removeTokens(T_WHITESPACE);
  }

  public function getUnused()
  {
    $uses   = $this->getUseStatements();
    $usages = $this->getUsages();
    $unused = array();

    foreach($uses as $use) {
      if (!in_array($use, $usages)) {
        $unused[] =  $use;
      }
    }
    return $unused;
  }

  public function getUsages()
  {
    $usages = array();

    foreach($this->content as $key => $token) {

      if (!is_string($token)) {
        $t = $this->content;

        // for static calls
        if ($token[0] == T_DOUBLE_COLON) {
          // only if it is NOT full or half qualified namespace
          if ($t[$key-2][0] != T_NAMESPACE) {
            $usages[] = $t[$key-1][1];
          }
        }

        // for object instanciations
        if ($token[0] == T_NEW) {
          if ($t[$key+2][0] != T_NAMESPACE) {
            $usages[] = $t[$key+1][1];
          }
        }

        // for class extensions
        if ($token[0] == T_EXTENDS || $token[0] == T_IMPLEMENTS) {
          if ($t[$key+2][0] != T_NAMESPACE) {
            $usages[] = $t[$key+1][1];
          }
        }

        // for catch blocks
        if ($token[0] == T_CATCH) {
          if ($t[$key+3][0] != T_NAMESPACE) {
            $usages[] = $t[$key+2][1];
          }
        }
      }
    }
    return array_values(array_unique($usages));
  }

  public function getUseStatements()
  {
    $tokenUses = array();
    $level = 0;

    foreach($this->content as $key => $token) {

      // for traits, only first level uses should be captured
      if (is_string($token)) {
        if ($token == '{') {
          $level++;
        }
        if ($token == '}') {
          $level--;
        }
      }

      // capture all use statements besides trait-uses in class
      if (!is_string($token) && $token[0] == T_USE && $level == 0) {
        $tokenUses[] = $key;
      }
    }

    $useStatements = array();

    // get rid of uses in lambda functions
    foreach($tokenUses as $key => $tokenKey) {
      $i                   = $tokenKey;
      $char                = '';
      $useStatements[$key] = '';

      while($char != ';') {
        ++$i;
        $char = is_string($this->content[$i]) ? $this->content[$i] : $this->content[$i][1];

        if (!is_string($this->content[$i]) && $this->content[$i][0] == T_AS) {
          $useStatements[$key] .= ' AS ';
        } else {
          $useStatements[$key] .= $char;
        }

        if ($char == '(') {
          unset($useStatements[$key]);
          break;
        }
      }
    }

    $allUses = array();

    // get all use statements
    foreach($useStatements as $fullStmt) {
      $fullStmt = rtrim($fullStmt, ';');
      $fullStmt = preg_replace('/^.+ AS /', '', $fullStmt);
      $fullStmt = explode(',', $fullStmt);

      foreach($fullStmt as $singleStmt) {
        // $singleStmt only for full qualified use
        $fqUses[] = $singleStmt;

        $singleStmt = explode('\\', $singleStmt);
        $allUses[] = array_pop($singleStmt);
      }
    }
    return $allUses;
  }

  public function removeTokens($tokenId)
  {
    foreach($this->content as $key => $token) {
      if (isset($token[0]) && $token[0] == $tokenId) {
        unset($this->content[$key]);
      }
    }
    // reindex
    $this->content = array_values($this->content);
  }

}

$unused = new UseStatementSanitizer('class.php');

print_r($unused->getUnused());

Returns:

Array
(
  [0] => NSname
  [1] => NSname1
)

10 Comments

Nice! I will give this a shot and let you know how it goes.
Do the use directives inside classes (for traits in PHP 5.4) and preceding closure blocks both have different token types than the namespace use? If not, you may want to make sure this code ignores those.
Good point, I knew, I have forgotten some cases, I will do a bit rework on the code soon.
I'm accepting your answer because it does the job. I just needed a quick script to tell me where I have unused use statements and this works good (aside from a few minor bugs). Thanks!
Your code lacks two use cases: $foo instanceof FooInterface and object typehinting foo(ClassOne $one, ClassTwo $too). Otherwise it seems to work nicely.
|
1

It would probably depend on the way your code is set up. If your code uses namespaces like so:

namespace Foo
{
   <one or more classes in namespace Foo>
}

then you're probably fine if you just check each file individually. That still means you would have to parse the PHP code to find the use statements, and then to determine which statements are used.

The easy way is to use a tool that's already been built. I recently started using PhpStorm IDE (30 day free trail, or the early access program) and that can inform you when you have unused use statements in a file (and you can even specify whether that should come up as warnings or errors). It would still require you to open each file though. But you could also check files you are editing, then eventually your code will be cleaner.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.