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

时间:2022-05-12 21:32:56

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?

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

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

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

3 个解决方案

#1


20  

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

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

#2


6  

EDIT

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

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

  • Traits and anonymous/lambda functions are ignored
  • Traits和anonymous / lambda函数被忽略

  • Now taking care of catch blocks, class extensions and interfaces
  • 现在处理catch块,类扩展和接口

  • Indentation and comments don't matter
  • 缩进和评论无关紧要

  • Multiple declarations for namespace aliases work too
  • 命名空间别名的多个声明也可以工作

  • Static and object class calls are recognized as "usage" ($u->getUsages())
  • 静态和对象类调用被识别为“用法”($ u-> getUsages())

  • Full and half qualified usages are not treated
  • 完整和半合格的用法不予处理

The test file, class.php:

测试文件,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
)
*/

#3


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.

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

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.

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

#1


20  

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

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

#2


6  

EDIT

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

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

  • Traits and anonymous/lambda functions are ignored
  • Traits和anonymous / lambda函数被忽略

  • Now taking care of catch blocks, class extensions and interfaces
  • 现在处理catch块,类扩展和接口

  • Indentation and comments don't matter
  • 缩进和评论无关紧要

  • Multiple declarations for namespace aliases work too
  • 命名空间别名的多个声明也可以工作

  • Static and object class calls are recognized as "usage" ($u->getUsages())
  • 静态和对象类调用被识别为“用法”($ u-> getUsages())

  • Full and half qualified usages are not treated
  • 完整和半合格的用法不予处理

The test file, class.php:

测试文件,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
)
*/

#3


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.

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

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.

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