Самый простой способ обнаружения/удаления неиспользуемых `use` -операций из PHP-кода

Я искал все для чего-то подобного, но я считаю, что слово "использование", возможно, слишком распространено для любых полезных результатов:

Каков самый простой способ удалить все неиспользуемые операторы use из файлов классов в кодовой базе PHP?

Изменить:. Для простоты мы можем игнорировать обнаружение операторов use, которые используются для аннотаций.

Ответ 2

ИЗМЕНИТЬ

Я полностью переписал его, так что теперь он намного мощнее:

  • Черты и анонимные/лямбда-функции игнорируются
  • Теперь ухаживаем за блоками catch, расширениями классов и интерфейсами
  • Отступы и комментарии не имеют значения.
  • Несколько объявлений для псевдонимов пространства имен тоже работают.
  • Статические вызовы классов объектов и классов распознаются как "использование" ($ u- > getUsages())
  • Полные и неполноценные использования не рассматриваются

Тестовый файл, 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;
}

И здесь 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

Вероятно, это зависит от того, как настроен ваш код. Если ваш код использует такие пространства имен:

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

тогда вы, вероятно, хорошо, если вы просто проверяете каждый файл по отдельности. Это все равно означает, что вам придется проанализировать PHP-код, чтобы найти операторы use, а затем определить, какие операторы используются.

Простым способом является использование уже созданного инструмента. Недавно я начал использовать PhpStorm IDE (30-дневный свободный трейл или ранний доступ программа), и это может сообщить вам, когда вы используете неиспользуемые операторы use в файле (и вы даже можете указать, должно ли оно появляться в виде предупреждений или ошибок). Тем не менее, вам все равно потребуется открыть каждый файл. Но вы также можете проверить файлы, которые вы редактируете, а затем, в конечном итоге, ваш код будет более чистым.