parametros - manejo de funciones en php
¿Cómo puedo encontrar funciones no utilizadas en un proyecto PHP? (8)
Debido a que las funciones / métodos de PHP pueden invocarse dinámicamente, no existe una forma programática de saber con certeza si nunca se llamará a una función.
La única forma segura es a través del análisis manual.
¿Cómo puedo encontrar funciones no utilizadas en un proyecto de PHP?
¿Hay funciones o API integradas en PHP que me permitan analizar mi base de código, por ejemplo, Reflection , token_get_all()
?
¿Estas API son lo suficientemente ricas para que no tenga que depender de una herramienta de terceros para realizar este tipo de análisis?
Este bit de scripts de bash puede ayudar:
grep -rhio ^function/ .*/( .|awk -F''[( ]'' ''{print "echo -n " $2 " && grep -rin " $2 " .|grep -v function|wc -l"}''|bash|grep 0
Esto básicamente greps recursivamente el directorio actual para las definiciones de funciones, pasa los hits a awk, que forma un comando para hacer lo siguiente:
- imprime el nombre de la función
- grep recursivamente de nuevo
- canalizar la salida a grep -v para filtrar las definiciones de función para retener las llamadas a la función
- canaliza esta salida a wc -l que imprime el recuento de líneas
Este comando luego se envía para ejecución a bash y la salida se grepped para 0, lo que indicaría 0 llamadas a la función.
Tenga en cuenta que esto no resolverá el problema que cita Clerbrown anteriormente, por lo que podría haber algunos falsos positivos en la salida.
Gracias Greg y Dave por los comentarios. No era exactamente lo que estaba buscando, pero decidí dedicar un poco de tiempo a investigarlo y se me ocurrió esta solución rápida y sucia:
<?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]);
}
}
}
?>
Probablemente pase más tiempo con él, así puedo encontrar rápidamente los archivos y los números de línea de las definiciones y referencias de funciones; esta información se está reuniendo, simplemente no se muestra.
Puedes probar el detector de código muerto de Sebastian Bergmann:
phpdcd
es un detector de código muerto (DCD) para código PHP. Escanea un proyecto de PHP para todas las funciones y métodos declarados y los informa como "código muerto" que no se invocan al menos una vez.
Fuente: https://github.com/sebastianbergmann/phpdcd
Tenga en cuenta que se trata de un analizador de código estático, por lo que podría proporcionar falsos positivos para los métodos que solo realizan llamadas dinámicas, por ejemplo, no puede detectar $foo = ''fn''; $foo();
$foo = ''fn''; $foo();
Puedes instalarlo a través de PEAR:
pear install phpunit/phpdcd-beta
Después de eso, puede usar con las siguientes opciones:
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.
Más herramientas:
Nota: según el aviso del repositorio, este proyecto ya no se mantiene y su repositorio solo se guarda para fines de archivo . Así que su millaje puede variar.
Si phpCallGraph no recuerdo, puede usar phpCallGraph para hacer eso. Te generará un buen gráfico (imagen) con todos los métodos involucrados. Si un método no está conectado a ningún otro, es una buena señal de que el método está huérfano.
Aquí hay un ejemplo: classGallerySystem.png
El método getKeywordSetOfCategories()
está huérfano.
Por cierto, no es necesario tomar una imagen: phpCallGraph también puede generar un archivo de texto, una matriz de PHP, etc.
USO: find_unused_functions.php <root_directory>
NOTA: Este es un enfoque ''rápido y sucio'' para el problema. Esta secuencia de comandos solo realiza un pase léxico sobre los archivos y no respeta las situaciones en las que diferentes módulos definen funciones o métodos con nombres idénticos. Si usa un IDE para su desarrollo de PHP, puede ofrecer una solución más completa.
Requiere PHP 5
Para guardar una copia y pegar, una descarga directa y cualquier versión nueva, están disponibles aquí .
#!/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 no hay forma. Para saber qué funciones "pertenecen a quién", necesitaría ejecutar el sistema (búsqueda de funciones de enlace tardío en tiempo de ejecución).
Pero las herramientas de refactorización se basan en el análisis de código estático. Me gustan mucho los lenguajes dinámicos, pero en mi opinión son difíciles de escalar. La falta de refactorizaciones seguras en grandes bases de código y lenguajes de tipado dinámico es un inconveniente importante para la mantenibilidad y la evolución del software de manejo.
phpxref identificará dónde se llaman las funciones desde las cuales se facilitaría el análisis, pero todavía hay una cierta cantidad de esfuerzo manual involucrado.