Bagaimana saya dapat menemukan fungsi yang tidak terpakai dalam proyek PHP

Bagaimana saya dapat menemukan apapun yang tidak terpakai fungsi dalam PHP project?

Ada fitur atau Api yang dibangun ke PHP yang akan memungkinkan saya untuk menganalisa saya codebase - misalnya Refleksi, token_get_all()?

Ini adalah Api ini kaya fitur yang cukup bagi saya untuk tidak harus bergantung pada alat pihak ketiga untuk melakukan jenis analisis?

Mengomentari pertanyaan (4)

Anda dapat mencoba Sebastian Bergmann's Kode Mati Detektor:

phpdcd adalah Kode Mati Detektor (DCD) untuk kode PHP. Ini scan PHP proyek untuk menyatakan fungsi dan metode dan laporan mereka sebagai "kode mati" yang tidak disebut setidaknya sekali.

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

Perhatikan bahwa itu's statis code analyzer, sehingga dapat memberikan hasil positif palsu bagi metode-metode yang hanya disebut secara dinamis, misalnya ia tidak dapat mendeteksi $foo = 'fn'; $foo();

Anda dapat menginstalnya melalui PEAR:

pear install phpunit/phpdcd-beta

Setelah itu anda dapat menggunakan opsi berikut:


Usage: phpdcd [switches] 
Komentar (5)
Larutan

Terima kasih Greg dan Dave untuk umpan balik. Itu't cukup apa yang saya cari, tapi saya memutuskan untuk menempatkan sedikit waktu untuk meneliti hal ini dan datang dengan cepat dan kotor solusi:

<?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'mungkin akan menghabiskan lebih banyak waktu di atasnya sehingga aku dapat dengan cepat menemukan file dan nomor baris dari definisi fungsi dan referensi, ini informasi yang dikumpulkan, hanya saja tidak ditampilkan.

Komentar (2)

Ini sedikit bash scripting mungkin bisa membantu:

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

Ini pada dasarnya secara rekursif greps direktori saat ini untuk definisi fungsi, melewati rumah untuk awk, yang membentuk sebuah perintah untuk melakukan hal-hal berikut:

  • cetak nama fungsi
  • secara rekursif grep untuk itu lagi
  • pipa keluaran untuk grep -v untuk menyaring definisi fungsi sehingga untuk mempertahankan panggilan ke fungsi
  • pipa output ini ke wc -l yang mencetak garis menghitung

Perintah ini kemudian dikirim untuk eksekusi bash dan output grepped untuk 0, yang akan menunjukkan 0 panggilan ke fungsi.

Perhatikan bahwa hal ini akan tidak memecahkan masalah calebbrown mengutip di atas, jadi mungkin ada beberapa positif palsu dalam output.

Komentar (0)

PENGGUNAAN: find_unused_functions.php <root_directory>

CATATAN: Ini adalah 'cepat-n-kotor' pendekatan untuk masalah ini. Script ini hanya melakukan leksikal melewati file, dan tidak menghormati situasi di mana modul yang berbeda menentukan identik bernama fungsi atau metode. Jika anda menggunakan IDE untuk pengembangan PHP, itu mungkin menawarkan solusi yang lebih komprehensif.

Membutuhkan PHP 5

Untuk menghemat copy dan paste, langsung download, dan setiap versi baru, yang tersedia.

#!/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 */
Komentar (1)

Jika saya ingat dengan benar, anda dapat menggunakan phpCallGraph untuk melakukan itu. It'akan menghasilkan grafik yang bagus (gambar) untuk anda dengan semua metode yang terlibat. Jika metode ini tidak terhubung ke yang lain, yang's pertanda baik bahwa metode adalah yatim piatu.

Berikut ini's contoh: classGallerySystem.png

Metode getKeywordSetOfCategories() adalah yatim piatu.

Hanya dengan cara itu, anda don't harus mengambil gambar -- phpCallGraph juga dapat generate file teks, atau array PHP, dll..

Komentar (0)

Karena fungsi PHP/metode yang dapat dipanggil secara dinamis, tidak ada cara program untuk mengetahui dengan pasti jika sebuah fungsi tidak akan pernah bisa disebut.

Satu-satunya cara tertentu melalui analisis manual.

Komentar (0)

2019+ Update

Aku punya inspied oleh Andrey's answer dan ternyata ini menjadi sebuah standar pengkodean mengendus.

Deteksi sangat sederhana namun kuat:

  • menemukan semua metode fungsi publik someMethod()
  • kemudian menemukan semua metode panggilan ${apa}->someMethod()
  • dan hanya laporan itu fungsi-fungsi publik yang tidak pernah disebut

Itu membantu saya untuk menghapus atas 20+ metode saya akan memiliki untuk mempertahankan dan tes.


3 Langkah untuk Menemukan mereka

Menginstal ECS:

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

Set up ecs.yaml config:

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

Jalankan perintah:

vendor/bin/ecs check src

Seperti diberitakan metode dan menghapus orang-orang yang anda don't baik-baik saja berguna 👍


Anda dapat membaca lebih lanjut tentang hal itu di sini: Menghapus Mati Metode Umum dari Kode

Komentar (2)

afaik tidak ada cara. Untuk mengetahui fungsi "adalah milik siapa" anda akan perlu untuk melaksanakan sistem (runtime ikatan fungsi lookup).

Tapi Refactoring yang sesuai didasarkan pada analisis kode statis. Aku benar-benar suka dinamis mengetik bahasa, tetapi dalam pandangan saya mereka sulit untuk skala. Kurangnya aman refactorings besar codebases dan dinamis mengetik bahasa adalah kelemahan utama untuk pemeliharaan dan penanganan evolusi perangkat lunak.

Komentar (0)

phpxref akan mengidentifikasi mana fungsi yang dipanggil dari yang akan memfasilitasi analisis - tapi ada's masih sejumlah upaya manual yang terlibat.

Komentar (0)