我如何在PHP项目中find未使用的函数

我如何在PHP项目中find任何未使用的函数?

有没有内置到PHP的function或API,将允许我分析我的代码库 – 例如reflection , token_get_all()

这些API是否足够丰富,使我不必依赖第三方工具来执行此类分析?

你可以试试塞巴斯蒂安·伯格曼的死码检测器:

phpdcd是一个用于PHP代码的死代码检测器(DCD)。 它会扫描所有已声明的函数和方法的PHP项目,并将它们报告为至less一次未被调用的“死代码”。

来源: https : //github.com/sebastianbergmann/phpdcd

请注意,它是一个静态代码分析器,所以它可能给只是dynamic调用的方法带来误报,例如它不能检测到$foo = 'fn'; $foo(); $foo = 'fn'; $foo();

你可以通过PEAR安装它:

 pear install phpunit/phpdcd-beta 

之后,您可以使用以下选项:

 Usage: phpdcd [switches] <directory|file> ... --recursive Report code as dead if it is only called by dead code. --exclude <dir> Exclude <dir> from code analysis. --suffixes <suffix> A comma-separated list of file suffixes to check. --help Prints this usage information. --version Prints the version and exits. --verbose Print progress bar. 

更多工具:


注意:根据存储库通知, 该项目不再维护,其存储库仅用于存档目的 。 所以你的里程可能会变化。

感谢Greg和Dave的反馈。 不是我正在寻找的东西,但我决定花一点时间研究它,并提出了这个快速和肮脏的解决scheme:

 <?php $functions = array(); $path = "/path/to/my/php/project"; define_dir($path, $functions); reference_dir($path, $functions); echo "<table>" . "<tr>" . "<th>Name</th>" . "<th>Defined</th>" . "<th>Referenced</th>" . "</tr>"; foreach ($functions as $name => $value) { echo "<tr>" . "<td>" . htmlentities($name) . "</td>" . "<td>" . (isset($value[0]) ? count($value[0]) : "-") . "</td>" . "<td>" . (isset($value[1]) ? count($value[1]) : "-") . "</td>" . "</tr>"; } echo "</table>"; function define_dir($path, &$functions) { if ($dir = opendir($path)) { while (($file = readdir($dir)) !== false) { if (substr($file, 0, 1) == ".") continue; if (is_dir($path . "/" . $file)) { define_dir($path . "/" . $file, $functions); } else { if (substr($file, - 4, 4) != ".php") continue; define_file($path . "/" . $file, $functions); } } } } function define_file($path, &$functions) { $tokens = token_get_all(file_get_contents($path)); for ($i = 0; $i < count($tokens); $i++) { $token = $tokens[$i]; if (is_array($token)) { if ($token[0] != T_FUNCTION) continue; $i++; $token = $tokens[$i]; if ($token[0] != T_WHITESPACE) die("T_WHITESPACE"); $i++; $token = $tokens[$i]; if ($token[0] != T_STRING) die("T_STRING"); $functions[$token[1]][0][] = array($path, $token[2]); } } } function reference_dir($path, &$functions) { if ($dir = opendir($path)) { while (($file = readdir($dir)) !== false) { if (substr($file, 0, 1) == ".") continue; if (is_dir($path . "/" . $file)) { reference_dir($path . "/" . $file, $functions); } else { if (substr($file, - 4, 4) != ".php") continue; reference_file($path . "/" . $file, $functions); } } } } function reference_file($path, &$functions) { $tokens = token_get_all(file_get_contents($path)); for ($i = 0; $i < count($tokens); $i++) { $token = $tokens[$i]; if (is_array($token)) { if ($token[0] != T_STRING) continue; if ($tokens[$i + 1] != "(") continue; $functions[$token[1]][1][] = array($path, $token[2]); } } } ?> 

我可能会花更多的时间,所以我可以快速find函数定义和引用的文件和行号; 这个信息正在被收集,只是没有显示。

这一点的bash脚本可能会有所帮助:

 grep -rhio ^function\ .*\( .|awk -F'[( ]' '{print "echo -n " $2 " && grep -rin " $2 " .|grep -v function|wc -l"}'|bash|grep 0 

这基本上recursion地greps当前目录的函数定义,传递命中awk,这形成一个命令执行以下操作:

  • 打印函数名称
  • recursiongrep再次
  • 输出到grep -v的pipe道过滤掉函数定义,以保留对函数的调用
  • 将此输出pipe道输出到wc -l,它打印行数

这个命令然后被发送执行到bash和输出被grepped为0,这将表明0调用该函数。

请注意,这并不能解决上面列举的问题,所以输出中可能会有一些误报。

如果我没有记错,你可以使用phpCallGraph来做到这一点。 它将为您带来所有涉及的方法,生成一个很好的graphics(图像)。 如果一个方法没有连接到任何其他方法,那么这个方法是孤立的。

这是一个例子: classGallerySystem.png

getKeywordSetOfCategories()方法是孤立的。

顺便说一句,你不必拍图像 – phpCallGraph也可以生成一个文本文件,或一个PHP数组等。

由于PHP函数/方法可以被dynamic调用,所以没有编程方法可以确定地知道函数是否永远不会被调用。

唯一确定的方法是通过手动分析。

用法: find_unused_functions.php <root_directory>

注意:这是一个“快速 – 肮脏”的方法来解决这个问题。 这个脚本只执行文件的词汇传递,并不考虑不同模块定义相同名称的函数或方法的情况。 如果您为PHP开发使用IDE,则可能提供更全面的解决scheme。

需要PHP 5

为了节省您的复制和粘贴,直接下载和任何新版本,都可以在这里find

 #!/usr/bin/php -f <?php // ============================================================================ // // find_unused_functions.php // // Find unused functions in a set of PHP files. // version 1.3 // // ============================================================================ // // Copyright (c) 2011, Andrey Butov. All Rights Reserved. // This script is provided as is, without warranty of any kind. // // http://www.andreybutov.com // // ============================================================================ // This may take a bit of memory... ini_set('memory_limit', '2048M'); if ( !isset($argv[1]) ) { usage(); } $root_dir = $argv[1]; if ( !is_dir($root_dir) || !is_readable($root_dir) ) { echo "ERROR: '$root_dir' is not a readable directory.\n"; usage(); } $files = php_files($root_dir); $tokenized = array(); if ( count($files) == 0 ) { echo "No PHP files found.\n"; exit; } $defined_functions = array(); foreach ( $files as $file ) { $tokens = tokenize($file); if ( $tokens ) { // We retain the tokenized versions of each file, // because we'll be using the tokens later to search // for function 'uses', and we don't want to // re-tokenize the same files again. $tokenized[$file] = $tokens; for ( $i = 0 ; $i < count($tokens) ; ++$i ) { $current_token = $tokens[$i]; $next_token = safe_arr($tokens, $i + 2, false); if ( is_array($current_token) && $next_token && is_array($next_token) ) { if ( safe_arr($current_token, 0) == T_FUNCTION ) { // Find the 'function' token, then try to grab the // token that is the name of the function being defined. // // For every defined function, retain the file and line // location where that function is defined. Since different // modules can define a functions with the same name, // we retain multiple definition locations for each function name. $function_name = safe_arr($next_token, 1, false); $line = safe_arr($next_token, 2, false); if ( $function_name && $line ) { $function_name = trim($function_name); if ( $function_name != "" ) { $defined_functions[$function_name][] = array('file' => $file, 'line' => $line); } } } } } } } // We now have a collection of defined functions and // their definition locations. Go through the tokens again, // and find 'uses' of the function names. foreach ( $tokenized as $file => $tokens ) { foreach ( $tokens as $token ) { if ( is_array($token) && safe_arr($token, 0) == T_STRING ) { $function_name = safe_arr($token, 1, false); $function_line = safe_arr($token, 2, false);; if ( $function_name && $function_line ) { $locations_of_defined_function = safe_arr($defined_functions, $function_name, false); if ( $locations_of_defined_function ) { $found_function_definition = false; foreach ( $locations_of_defined_function as $location_of_defined_function ) { $function_defined_in_file = $location_of_defined_function['file']; $function_defined_on_line = $location_of_defined_function['line']; if ( $function_defined_in_file == $file && $function_defined_on_line == $function_line ) { $found_function_definition = true; break; } } if ( !$found_function_definition ) { // We found usage of the function name in a context // that is not the definition of that function. // Consider the function as 'used'. unset($defined_functions[$function_name]); } } } } } } print_report($defined_functions); exit; // ============================================================================ function php_files($path) { // Get a listing of all the .php files contained within the $path // directory and its subdirectories. $matches = array(); $folders = array(rtrim($path, DIRECTORY_SEPARATOR)); while( $folder = array_shift($folders) ) { $matches = array_merge($matches, glob($folder.DIRECTORY_SEPARATOR."*.php", 0)); $moreFolders = glob($folder.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR); $folders = array_merge($folders, $moreFolders); } return $matches; } // ============================================================================ function safe_arr($arr, $i, $default = "") { return isset($arr[$i]) ? $arr[$i] : $default; } // ============================================================================ function tokenize($file) { $file_contents = file_get_contents($file); if ( !$file_contents ) { return false; } $tokens = token_get_all($file_contents); return ($tokens && count($tokens) > 0) ? $tokens : false; } // ============================================================================ function usage() { global $argv; $file = (isset($argv[0])) ? basename($argv[0]) : "find_unused_functions.php"; die("USAGE: $file <root_directory>\n\n"); } // ============================================================================ function print_report($unused_functions) { if ( count($unused_functions) == 0 ) { echo "No unused functions found.\n"; } $count = 0; foreach ( $unused_functions as $function => $locations ) { foreach ( $locations as $location ) { echo "'$function' in {$location['file']} on line {$location['line']}\n"; $count++; } } echo "=======================================\n"; echo "Found $count unused function" . (($count == 1) ? '' : 's') . ".\n\n"; } // ============================================================================ /* EOF */ 

afaik没有办法。 要知道哪些function“属于谁”,您需要执行系统(运行时后期绑定function查找)。

但是重构工具是基于静态代码分析的。 我真的很喜欢dynamictypes的语言,但在我看来,他们很难扩展。 在大型代码库和dynamictypes语言中缺乏安全的重构是可维护性和处理软件演化的主要缺点。

phpxref将识别函数的调用方式,从而便于分析 – 但是仍然需要一定的手动操作。