Cum pot găsi funcții neutilizate într-un proiect PHP

Cum pot găsi orice funcții neutilizate într-un proiect PHP?

Există funcții sau API-uri construite în PHP care îmi vor permite să analizez codul meu - de exemplu Reflecție , token_get_all() ?

Sunt aceste interfețe API suficient de bogate pentru a nu trebui să se bazeze pe un instrument terț pentru a efectua acest tip de analiză?

0
fr hi bn
adăugat autor Greg Hurlman, sursa
Găsesc că o căutare simplă a fișierelor poate face deja deja - atâta timp cât nu utilizați nume de funcții variabile și altele asemenea.
adăugat autor Pekka 웃, sursa
xdebug vă poate spune că cu analiza de acoperire a codului xdebug.org/docs/code_coverage
adăugat autor Mchl, sursa
Xdebug poate oferi și acoperire cu cod . Aveți posibilitatea să o utilizați în combinație cu fișierele auto_prepend_file și auto_append_file directivele ini pentru a înregistra codul de acoperire al aplicației dvs. în timpul generalizării utilizare.
adăugat autor Dave Marshall, sursa

8 răspunsuri

Mulțumesc lui Greg și lui Dave pentru feedback. Nu a fost exact ceea ce căutam, dar am decis să pun un pic de timp în cercetarea ei și am venit cu această soluție rapidă și murdară:

<?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]);
            }
        }
    }
?>

Probabil că voi petrece mai mult timp pe ea pentru a putea găsi rapid fișierele și numerele de linie ale definițiilor și referințelor funcțiilor; această informație este adunată, nu este afișată.

0
adăugat
Această soluție este bună dacă nu folosiți niciodată call_user_func() sau call_user_func_array()
adăugat autor calebbrown, sursa

afaik nu există nici o cale. Pentru a ști care dintre funcțiile "aparțin cui" ar trebui să executați sistemul (funcția de căutare întârziată de căutare).

Instrumentele Refactoring se bazează pe analiza codului static. Chiar îmi plac limbile dinamice tipărite, dar, în opinia mea, sunt greu de redus. Lipsa unor refactorizări sigure în baze de coduri mari și limbi dinamice tipizate reprezintă un dezavantaj major pentru evoluția software-ului de întreținere și manipulare.

0
adăugat

Puteți încerca Detectorul de coduri al lui Sebastian Bergmann:

phpdcd este un Detector de coduri de eroare (DCD) pentru cod PHP. Acesta scanează un proiect PHP pentru toate funcțiile și metodele declarate și le raportează ca fiind "cod mort" care nu sunt chemați cel puțin o dată.

Source: 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();

Puteți să o instalați prin intermediul PEAR:

pear install phpunit/phpdcd-beta

După care puteți utiliza următoarele opțiuni:

Usage: phpdcd [switches]  ...

--recursive Report code as dead if it is only called by dead code.

--exclude  Exclude  from code analysis.
--suffixes  A comma-separated list of file suffixes to check.

--help Prints this usage information.
--version Prints the version and exits.

--verbose Print progress bar.

Mai multe unelte:


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.

0
adăugat
Vă mulțumim pentru linkurile "mai multe instrumente", care se referă, de asemenea, la "Detectorul PHP Mess" ( phpmd.org )
adăugat autor BurninLeo, sursa
@BurhanAli nu mai este menținută nu înseamnă neapărat că nu mai funcționează. Nu ezitați să preluați întreținerea.
adăugat autor Gordon, sursa
@Dereckson da, sunt de acord. dar până când cineva face acest lucru în sfârșit, acest este mai bun decât nimic.
adăugat autor Gordon, sursa
+1: Nu știu asta
adăugat autor Mchl, sursa
@Gordon Există un motiv pentru care nu este menținut, spectrul larg de metode solicită nu detectează: "Metoda de clasă ()" și constructorii sunt raportați ca nefolosiți, de exemplu. Abordarea de reflecție nu este ideală pentru un limbaj dinamic și, prin urmare, îmbunătățirea instrumentului ar însemna în realitate rescrierea unei noi, utilizând o altă abordare pentru a detecta metodele folosite.
adăugat autor Dereckson, sursa
De pe pagina github: "Acest proiect nu mai este menținut, iar depozitul său este păstrat doar în scopuri de arhivare".
adăugat autor Burhan Ali, sursa

phpxref will identify where functions are called from which would facilitate the analysis - but there's still a certain amount of manual effort involved.

0
adăugat

Deoarece funcțiile/metodele PHP pot fi invocate în mod dinamic, nu există o modalitate programatică de a ști cu certitudine dacă o funcție nu va fi apelată niciodată.

Singurul mod sigur este analiza manuală.

0
adăugat

USAGE: find_unused_functions.php

NOTĂ: Acesta este un "rapid-n-murdar"? abordarea problemei. Acest script efectuează doar o trecere lexicală asupra fișierelor și nu respectă situațiile în care diferite module definesc funcții sau metode numite identic. Dacă utilizați un IDE pentru dezvoltarea PHP, acesta poate oferi o soluție mai cuprinzătoare.

Necesită PHP 5

Pentru a vă salva o copie și o lipire, o descărcare directă și orice versiuni noi sunt disponibil aici .

#!/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 \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 */
0
adăugat
Doar minunat, mersi Andrey! Am transformat acest lucru în codificator standard cu multă asistență: github.com/Symplify/Symplify/pull/466
adăugat autor Tomáš Votruba, sursa

Dacă îmi amintesc corect, puteți folosi phpCallGraph pentru a face acest lucru. Acesta va genera un grafic frumos (imagine) pentru tine cu toate metodele implicate. Dacă o metodă nu este conectată la nicio altă metodă, este un semn bun că metoda este orfană.

Here's an example: classGallerySystem.png

Metoda getKeywordSetOfCategories() este orfană.

Apropo, nu trebuie să faceți o imagine - phpCallGraph poate, de asemenea, genera un fișier text sau o matrice PHP etc.

0
adăugat

Acest script de bash ar putea ajuta:

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

Acest lucru face în mod real recursiv directorul curent pentru definirea funcțiilor, trece hits-urile către awk, care formează o comandă pentru a face următoarele:

  • imprimați numele funcției
  • în mod recursiv grep pentru el din nou
  • conductele care ies la grep -v pentru a filtra definițiile funcțiilor astfel încât să păstreze apelurile către funcție
  • conduce această ieșire la wc -l care imprimă numărul liniei

Această comandă este apoi trimisă spre executare pentru bash și ieșirea este grepată pentru 0, ceea ce ar indica 0 apeluri către funcție.

Rețineți că aceasta nu va rezolva problema Calebbrown citează mai sus, astfel încât ar putea exista unele rezultate false în ieșire.

0
adăugat
PHP România, Moldova
PHP România, Moldova
173 participanți

Vorbim despre Yii, Laravel, Symphony, MySQL, PgSQL, WP, OpenCart... Pentru confort, opriți notificările. Parteneri: https://ciupacabra.com @js_ro @node_ro @python_ro @seo_ro @Romania_Bot Offtop: @holywars_ro Joburi: @php_job @Grupuri_IT