PHP 프로젝트에서 사용하지 않는 함수를 어떻게 찾을 수 있나요?

StackOverflow https://stackoverflow.com/questions/11532

  •  08-06-2019
  •  | 
  •  

문제

PHP 프로젝트에서 사용하지 않는 함수를 어떻게 찾을 수 있나요?

내 코드베이스를 분석할 수 있는 기능이나 API가 PHP에 내장되어 있습니까? 예를 들어 반사, token_get_all()?

이러한 유형의 분석을 수행하기 위해 타사 도구에 의존할 필요가 없을 만큼 이러한 API 기능이 충분히 풍부합니까?

도움이 되었습니까?

해결책 2

피드백을 주신 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]);
            }
        }
    }
?>

함수 정의와 참조의 파일과 줄 번호를 빨리 찾을 수 있도록 좀 더 시간을 투자할 것입니다.이 정보는 수집되고 있지만 표시되지는 않습니다.

다른 팁

Sebastian Bergmann의 데드 코드 감지기를 사용해 볼 수 있습니다.

phpdcd PHP 코드용 DCD(Dead Code Detector)입니다.이는 선언된 모든 함수와 메서드에 대해 PHP 프로젝트를 검색하고 이를 적어도 한 번 호출되지 않은 "데드 코드"로 보고합니다.

원천: https://github.com/sebastianbergmann/phpdcd

이는 정적 코드 분석기이므로 동적으로만 호출되는 메서드에 대해 거짓 긍정을 제공할 수 있습니다.그것은 감지할 수 없다 $foo = 'fn'; $foo();

PEAR를 통해 설치할 수 있습니다.

pear install phpunit/phpdcd-beta

그 후에는 다음 옵션을 사용할 수 있습니다.

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.

추가 도구:


메모: 저장소 공지에 따라, 이 프로젝트는 더 이상 유지 관리되지 않으며 해당 저장소는 보관 목적으로만 보관됩니다..따라서 마일리지가 다를 수 있습니다.

이 bash 스크립팅이 도움이 될 수 있습니다.

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

이는 기본적으로 함수 정의를 위해 현재 디렉터리를 재귀적으로 파악하고 조회수를 awk에 전달하여 다음을 수행하는 명령을 구성합니다.

  • 함수 이름을 인쇄하세요
  • 재귀적으로 다시 grep
  • 해당 출력을 grep -v로 파이핑하여 함수에 대한 호출을 유지하기 위해 함수 정의를 필터링합니다.
  • 이 출력을 wc -l로 파이프하여 줄 수를 인쇄합니다.

그런 다음 이 명령은 실행을 위해 bash로 전송되고 출력은 0에 대해 grep됩니다. 이는 함수 호출이 0번임을 나타냅니다.

참고하세요. ~ 아니다 calebbrown이 위에서 인용한 문제를 해결하면 출력에 일부 오탐지가 있을 수 있습니다.

용법: find_unused_functions.php <루트_디렉토리>

메모:이는 문제에 대한 '빠른 앤 더티(quick-n-dirty)' 접근 방식입니다.이 스크립트는 파일에 대한 어휘 전달만 수행하며, 서로 다른 모듈이 동일한 이름의 함수 또는 메서드를 정의하는 상황을 고려하지 않습니다.PHP 개발에 IDE를 사용하는 경우 보다 포괄적인 솔루션을 제공할 수 있습니다.

PHP 5 필요

복사하여 붙여넣기, 직접 다운로드 및 새 버전을 저장하려면 여기에서 사용 가능.

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

내가 올바르게 기억한다면 당신은 사용할 수 있습니다 phpCall그래프 하기 위해서.관련된 모든 방법을 사용하여 멋진 그래프(이미지)를 생성합니다.메서드가 다른 메서드에 연결되어 있지 않으면 해당 메서드가 고아라는 좋은 신호입니다.

예는 다음과 같습니다. classGallerySystem.png

방법 getKeywordSetOfCategories() 고아입니다.

그런데, 이미지를 찍을 필요는 없습니다 -- phpCallGraph도 가능합니다 생성하다 텍스트 파일, PHP 배열 등.

PHP 함수/메서드는 동적으로 호출될 수 있으므로 함수가 호출되지 않는지 확실하게 알 수 있는 프로그래밍 방식은 없습니다.

유일한 확실한 방법은 수동 분석을 통해서입니다.

2019+ 업데이트

나는 영감을 얻었습니다. 안드레이의 답변 이것을 코딩 표준 스니프로 바꿨습니다.

탐지는 매우 간단하면서도 강력합니다.

  • 모든 메소드를 찾습니다 public function someMethod()
  • 그런 다음 모든 메소드 호출을 찾으십시오. ${anything}->someMethod()
  • 그리고 간단하게 호출되지 않은 공개 함수를 보고합니다.

제거하는데 도움이 됐어요 ~ 위에 20개 이상의 메소드 유지하고 테스트해야 할 것입니다.


그들을 찾는 3단계

ECS를 설치합니다.

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

설정 ecs.yaml 구성:

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

다음 명령을 실행하십시오.

vendor/bin/ecs check src

보고된 방법을 확인하고 유용하지 않은 방법을 제거하세요 👍


자세한 내용은 여기에서 읽어보세요. 코드에서 죽은 공용 메서드 제거

afaik 방법이 없습니다.어떤 기능이 "누구에게 속하는지" 알려면 시스템을 실행해야 합니다(런타임 지연 바인딩 기능 조회).

그러나 리팩토링 도구는 정적 코드 분석을 기반으로 합니다.나는 동적 유형 언어를 정말 좋아하지만 내 생각에는 확장하기가 어렵습니다.대규모 코드베이스와 동적 유형 언어의 안전한 리팩토링 부족은 유지 관리 및 소프트웨어 진화 처리에 있어 주요 단점입니다.

phpref 분석을 용이하게 하는 함수가 호출되는 위치를 식별하지만 여전히 어느 정도의 수동 작업이 필요합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top