Easiest way to detect/remove unused `use` statements from PHP codebase

前端 未结 3 1490
有刺的猬
有刺的猬 2021-02-13 05:47

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-us

相关标签:
3条回答
  • 2021-02-13 06:31

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

    0 讨论(0)
  • 2021-02-13 06:34

    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:

    <?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:

    <?php
    class UseStatementSanitzier
    {
      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 UseStatementSanitzier('class.php');
    
    print_r($unused->getUnused());
    
    /*
    Returns:
    Array
    (
      [0] => NSname
      [1] => NSname1
    )
    */
    
    0 讨论(0)
  • 2021-02-13 06:50

    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.

    0 讨论(0)
提交回复
热议问题