Vra

Hoe kan ek enige ongebruikte funksies vind in 'n PHP-projek?

Is daar funksies of APIs gebou in PHP wat jou sal toelaat om my kodebasis analiseer - byvoorbeeld Refleksie , token_get_all() ?

Is hierdie API funksie ryk genoeg vir my om nie te moet staatmaak op 'n derde instrument party tot hierdie tipe van analise uit te voer?

Was dit nuttig?

Oplossing 2

Dankie Greg en Dave vir die terugvoer. Was nie heeltemal wat ek was op soek na, maar ek het besluit om 'n bietjie van die tyd in die ondersoek na dit sit en vorendag gekom met hierdie vinnige en vuil oplossing:

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

Ek sal waarskynlik 'n paar meer tyd op dit spandeer so ek kan vinnig die lêers en lyn nommers van die funksie definisies en verwysings te vind; hierdie inligting word versamel, net nie vertoon word nie.

Ander wenke

Jy kan probeer Sebastian Bergmann se Dooie Code Aanwyser:

  

phpdcd is 'n Dooie Code Aanwyser (DCD) vir PHP-kode. Dit skanderings 'n PHP-projek vir al verklaar funksies en metodes en verslae wat as "dooie kode" wat nie ten minste een keer genoem word.

Bron: https://github.com/sebastianbergmann/phpdcd

Let daarop dat dit 'n statiese-kode ontleder, so dit mag dalk vals positiewes gee vir metodes wat net dinamies genoem, bv dit kan nie $foo = 'fn'; $foo(); spoor

Jy kan dit via PEAR installeer:

pear install phpunit/phpdcd-beta

Na wat jy kan gebruik met die volgende opsies:

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.

Meer gereedskap:


Nota: as per die kennisgewing repository, hierdie projek is nie meer in stand gehou en sy bron is net gehou vir argivale doeleindes . So jou kilometers kan wissel.

Dit bietjie bash script kan help:

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

Dit basies rekursief greps die huidige gids vir funksie definisies, verby die treffers te awk, wat 'n bevel vorm om die volgende te doen:

  • druk die funksie naam
  • rekursief grep vir dit weer
  • pype wat uitloop op grep v te filter funksie definisies so as om oproepe na die funksie behou
  • pype hierdie uitset na -l wat die lyn telling
  • druk toilet

Hierdie opdrag is dan gestuur vir uitvoering te bash en die uitset is grepped vir 0, wat 0 oproepe na die funksie sal aandui.

Let daarop dat hierdie sal nie op te los die probleem calebbrown noem hierbo, so daar dalk 'n paar vals positiewes in die uitset.

Gebruik: find_unused_functions.php

Nota: Hierdie is 'n "quick-N-vuil" benadering tot die probleem. Dit script net voer 'n leksikale gaan oor na die lêers, en nie situasies waar verskillende modules definieer identies met die naam funksies of metodes te respekteer. As jy 'n IDE gebruik vir jou PHP-ontwikkeling, kan dit 'n meer omvattende oplossing bied.

Vereis PHP 5

Om te red jy 'n kopie en plak, 'n direkte aflaai, en enige nuwe weergawes, is beskikbaar hier .

#!/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 */

As ek reg onthou wat jy kan gebruik phpCallGraph om dit te doen. Dit sal 'n lekker grafiek (beeld) vir jou met al die betrokke metodes te genereer. As 'n metode nie gekoppel is aan enige ander, dit is 'n goeie teken dat die metode is wat wees gelaat is.

Hier is 'n voorbeeld: classGallerySystem.png

Die metode getKeywordSetOfCategories() is wees gelaat.

Just by the way, het jy nie 'n beeld te neem - phpCallGraph kan ook genereer 'n tekslêer, of 'n PHP array, ens ..

As gevolg PHP funksies / metodes kan dinamiese drie maande, is daar geen programmatiese manier om te weet met sekerheid as 'n funksie sal nooit genoem word.

Die enigste sekere manier is deur middel van ontleding per hand.

2019+ Update

Ek het inspied deur Andrey se beantwoord en draai dit in 'n kodering standaard snuif.

Die opsporing is nog baie eenvoudig kragtige:

  • soek alle metodes public function someMethod()
  • dan vind al metode noem ${anything}->someMethod()
  • en eenvoudig verslae wat openbare funksies wat nooit genoem

Dit het my gehelp om oor 20+ metodes ek wil hê om in stand te hou en toets.

3 Stappe om hulle

Vind

Installeer ECS:

composer require symplify/easy-coding-standard --dev

Stel ecs.yaml config:

# ecs.yaml
services:
    Symplify\CodingStandard\Sniffs\DeadCode\UnusedPublicMethodSniff: ~

Voer die opdrag:

vendor/bin/ecs check src

Sien berig metodes en verwyder wat jy doen nie fyn nuttig

AFAIK daar is geen manier. Om te weet watter funksies "is wat deel uitmaak van wie" jy nodig sou wees om die stelsel (runtime laat bindende funksie lookup) uit te voer.

Maar refactoring gereedskap is gebaseer op statiese kode analise. Ek het regtig soos dinamiese getik tale, maar in my mening is dit moeilik om te skaal. Die gebrek aan veilige refactorings in groot codebases en dinamiese getikte tale is 'n groot nadeel vir instandhouding en die hantering van programmatuur evolusie.

phpxref sal identifiseer waar funksies genoem waaruit sou die ontleding te vergemaklik - maar daar is nog 'n sekere bedrag van handleiding moeite wat betrokke is.

Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top