从PHP代码库中检测/删除未使用的“use”语句的最简单方法


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

我到处都找过这样的东西,但我认为"使用"这个词可能太常见了,不适合任何有用的结果:

从PHP代码库中的类文件中删除所有未使用的use语句的最简单方法是什么?

编辑:为了简单起见,我们可以忽略检测用于注释的use语句。

检查FriendsOfPHP的PHP CS Fixerhttps://github.com/FriendsOfPHP/PHP-CS-Fixer

编辑

我已经完全重写了它,所以现在它更强大了:

  • 忽略特性和匿名/lambda函数
  • 现在处理catch块、类扩展和接口
  • 缩进和注释无关紧要
  • 命名空间别名的多个声明也适用
  • 静态和对象类调用被识别为";用法";($u->getUsages())
  • 不处理完全和一半合格的用途

测试文件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;
}

这里的脚本:

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

退货:

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

这可能取决于代码的设置方式。如果您的代码使用这样的名称空间:

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

那么,如果你只是单独检查每个文件,你可能就没事了。这仍然意味着您必须解析PHP代码以找到use语句,然后确定使用了哪些语句。

简单的方法是使用已经构建好的工具。我最近开始使用PhpStorm IDE(30天免费试用,或早期访问程序),当你在文件中有未使用的use语句时,它可以通知你(你甚至可以指定这应该是警告还是错误)。不过,它仍然需要您打开每个文件。但你也可以检查你正在编辑的文件,这样你的代码最终会更干净。