如何在 PHP 项目中找到未使用的函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11532/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I find unused functions in a PHP project
提问by Stacey Richards
How can I find any unused functions in a PHP project?
如何在 PHP 项目中找到任何未使用的函数?
Are there features or APIs built into PHP that will allow me to analyse my codebase - for example Reflection, token_get_all()?
是否有内置到 PHP 中的功能或 API 可以让我分析我的代码库 - 例如反射,token_get_all()?
Are these APIs feature rich enough for me not to have to rely on a third party tool to perform this type of analysis?
这些 API 的功能是否足够丰富,让我不必依赖第三方工具来执行此类分析?
采纳答案by Stacey Richards
Thanks Greg and Dave for the feedback. Wasn't quite what I was looking for, but I decided to put a bit of time into researching it and came up with this quick and dirty solution:
感谢 Greg 和 Dave 的反馈。不是我想要的,但我决定花一些时间研究它,并提出了这个快速而肮脏的解决方案:
<?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]);
}
}
}
?>
I'll probably spend some more time on it so I can quickly find the files and line numbers of the function definitions and references; this information is being gathered, just not displayed.
我可能会花更多的时间在上面,以便我可以快速找到函数定义和引用的文件和行号;正在收集这些信息,只是不显示。
回答by Gordon
You can try Sebastian Bergmann's Dead Code Detector:
你可以试试 Sebastian Bergmann 的死代码检测器:
phpdcdis a Dead Code Detector (DCD) for PHP code. It scans a PHP project for all declared functions and methods and reports those as being "dead code" that are not called at least once.
phpdcd是一个用于 PHP 代码的死代码检测器 (DCD)。它会扫描 PHP 项目中所有声明的函数和方法,并将它们报告为至少未调用一次的“死代码”。
Source: https://github.com/sebastianbergmann/phpdcd
来源:https: //github.com/sebastianbergmann/phpdcd
Note that it's a static code analyzer, so it might give false positives for methods that only called dynamically, e.g. it cannot detect $foo = 'fn'; $foo();
请注意,它是一个静态代码分析器,因此它可能会为仅动态调用的方法提供误报,例如它无法检测 $foo = 'fn'; $foo();
You can install it via PEAR:
您可以通过 PEAR 安装它:
pear install phpunit/phpdcd-beta
After that you can use with the following options:
之后,您可以使用以下选项:
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.
More tools:
更多工具:
Note:as per the repository notice, this project is no longer maintained and its repository is only kept for archival purposes. So your mileage may vary.
注意:根据存储库通知,该项目不再维护,其存储库仅用于存档目的。所以你的里程可能会有所不同。
回答by Tim Cullen
This bit of bash scripting might help:
这段 bash 脚本可能会有所帮助:
grep -rhio ^function\ .*\( .|awk -F'[( ]' '{print "echo -n " " && grep -rin " " .|grep -v function|wc -l"}'|bash|grep 0
This basically recursively greps the current directory for function definitions, passes the hits to awk, which forms a command to do the following:
这基本上以递归方式 grep 函数定义的当前目录,将命中传递给 awk,它形成一个命令来执行以下操作:
- print the function name
- recursively grep for it again
- piping that output to grep -v to filter out function definitions so as to retain calls to the function
- pipes this output to wc -l which prints the line count
- 打印函数名
- 再次递归地grep
- 管道输出到 grep -v 以过滤掉函数定义以保留对函数的调用
- 将此输出通过管道传输到 wc -l 以打印行数
This command is then sent for execution to bash and the output is grepped for 0, which would indicate 0 calls to the function.
然后将此命令发送到 bash 以执行,并且输出为 0,这表示对该函数的调用为 0。
Note that this will notsolve the problem calebbrown cites above, so there might be some false positives in the output.
请注意,这不会解决上面 calebbrown 引用的问题,因此输出中可能存在一些误报。
回答by Andrey Butov
USAGE: find_unused_functions.php <root_directory>
用法:find_unused_functions.php <root_directory>
NOTE: This is a ‘quick-n-dirty' approach to the problem. This script only performs a lexical pass over the files, and does not respect situations where different modules define identically named functions or methods. If you use an IDE for your PHP development, it may offer a more comprehensive solution.
注意:这是解决问题的“快速n-dirty”方法。此脚本仅对文件执行词法传递,并且不考虑不同模块定义相同命名的函数或方法的情况。如果您使用 IDE 进行 PHP 开发,它可能会提供更全面的解决方案。
Requires PHP 5
需要 PHP 5
To save you a copy and paste, a direct download, and any new versions, are available here.
为了节省您的复制和粘贴,可以在此处获得直接下载和任何新版本。
#!/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 */
回答by Till
If I remember correctly you can use phpCallGraphto do that. It'll generate a nice graph (image) for you with all the methods involved. If a method is not connected to any other, that's a good sign that the method is orphaned.
如果我没记错的话,您可以使用phpCallGraph来做到这一点。它将使用所有涉及的方法为您生成一个漂亮的图形(图像)。如果一个方法未连接到任何其他方法,则表明该方法是孤立的。
Here's an example: classGallerySystem.png
这是一个例子:classGallerySystem.png
The method getKeywordSetOfCategories()is orphaned.
该方法getKeywordSetOfCategories()是孤立的。
Just by the way, you don't have to take an image -- phpCallGraph can also generatea text file, or a PHP array, etc..
顺便说一句,您不必拍摄图像——phpCallGraph 还可以生成文本文件,或 PHP 数组等。
回答by webbiedave
Because PHP functions/methods can be dynamically invoked, there is no programmatic way to know with certainty if a function will never be called.
因为 PHP 函数/方法可以动态调用,所以没有程序化的方式来确定某个函数是否永远不会被调用。
The only certain way is through manual analysis.
唯一确定的方法是通过手动分析。
回答by Tomá? Votruba
2019+ Update
2019+ 更新
I got inspied by Andrey's answerand turned this into a coding standard sniff.
我被安德烈的回答启发了,并将其变成了编码标准嗅探。
The detection is very simple yet powerful:
检测非常简单但功能强大:
- finds all methods
public function someMethod() - then find all method calls
${anything}->someMethod() - and simply reports those public functions that were never called
- 找到所有方法
public function someMethod() - 然后找到所有方法调用
${anything}->someMethod() - 并简单地报告那些从未被调用过的公共函数
It helped me to remove over20+ methodsI would have to maintain and test.
3 Steps to Find them
找到它们的 3 个步骤
Install ECS:
安装 ECS:
composer require symplify/easy-coding-standard --dev
Set up ecs.yamlconfig:
设置ecs.yaml配置:
# ecs.yaml
services:
Symplify\CodingStandard\Sniffs\DeadCode\UnusedPublicMethodSniff: ~
Run the command:
运行命令:
vendor/bin/ecs check src
See reported methods and remove those you don't fine useful
查看报告的方法并删除那些你认为没有用的方法
You can read more about it here: Remove Dead Public Methods from Your Code
您可以在此处阅读更多相关信息:从您的代码中删除无效的公共方法
回答by symcbean
回答by manuel aldana
afaik there is no way. To know which functions "are belonging to whom" you would need to execute the system (runtime late binding function lookup).
afaik 没有办法。要知道哪些函数“属于谁”,您需要执行系统(运行时后期绑定函数查找)。
But Refactoring tools are based on static code analysis. I really like dynamic typed languages, but in my view they are difficult to scale. The lack of safe refactorings in large codebases and dynamic typed languages is a major drawback for maintainability and handling software evolution.
但是重构工具是基于静态代码分析的。我真的很喜欢动态类型语言,但在我看来它们很难扩展。在大型代码库和动态类型语言中缺乏安全重构是可维护性和处理软件演化的主要缺点。

