• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemsdk / phpcpd / #6

23 Mar 2025 04:51PM UTC coverage: 75.711% (-0.08%) from 75.789%
#6

push

DKravtsov
phpcpd 8.1.0 release. Added Suffix Tree-based algorithm for code clone detection (experimental), added progress bar. Made codebase refactoring. Updated packages: sebastian/cli-parser, sebastian/version, phpunit/php-file-iterator, phpunit/php-timer. Updated tests to the PHPUnit 12.

101 of 126 new or added lines in 10 files covered. (80.16%)

1 existing line in 1 file now uncovered.

692 of 914 relevant lines covered (75.71%)

3.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Cli/Application.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Systemsdk\PhpCPD\Cli;
6

7
use SebastianBergmann\FileIterator\Facade;
8
use SebastianBergmann\Timer\ResourceUsageFormatter;
9
use SebastianBergmann\Timer\Timer;
10
use SebastianBergmann\Version;
11
use Systemsdk\PhpCPD\Detector\Detector;
12
use Systemsdk\PhpCPD\Detector\Strategy\AbstractStrategy;
13
use Systemsdk\PhpCPD\Detector\Strategy\DefaultStrategy;
14
use Systemsdk\PhpCPD\Detector\Strategy\StrategyConfiguration;
15
use Systemsdk\PhpCPD\Detector\Strategy\SuffixTreeStrategy;
16
use Systemsdk\PhpCPD\Exceptions\Exception;
17
use Systemsdk\PhpCPD\Exceptions\InvalidStrategyException;
18
use Systemsdk\PhpCPD\Exceptions\LoggerException;
19
use Systemsdk\PhpCPD\Exceptions\ProcessingResultException;
20
use Systemsdk\PhpCPD\Log\PMD;
21
use Systemsdk\PhpCPD\Log\Text;
22

23
use function count;
24
use function dirname;
25
use function printf;
26

27
use const PHP_EOL;
28

29
final class Application
30
{
31
    public const string VERSION = '8.1.0';
32

33
    /**
34
     * @param array<int, string> $argv
35
     */
36
    public function run(array $argv): int
37
    {
38
        $this->printVersion();
×
39

40
        try {
41
            $arguments = (new ArgumentsBuilder())->build($argv);
×
42
        } catch (Exception $exception) {
×
43
            print PHP_EOL . $exception->getMessage() . PHP_EOL;
×
44

45
            return 1;
×
46
        }
47

48
        print PHP_EOL;
×
49

50
        if ($arguments->version()) {
×
51
            return 0;
×
52
        }
53

54
        if ($arguments->help()) {
×
55
            $this->help();
×
56

57
            return 0;
×
58
        }
59

60
        /** @var list<non-empty-string> $paths */
61
        $paths = $arguments->directories();
×
62
        /** @var list<non-empty-string> $suffixes */
63
        $suffixes = $arguments->suffixes();
×
64
        /** @var list<non-empty-string> $exclude */
65
        $exclude = $arguments->exclude();
×
66
        $files = (new Facade())->getFilesAsArray(
×
67
            $paths,
×
68
            $suffixes,
×
69
            '',
×
70
            $exclude
×
71
        );
×
72

73
        if (empty($files)) {
×
74
            print 'No files found to scan' . PHP_EOL;
×
75

76
            return 1;
×
77
        }
78

79
        try {
80
            $strategy = $this->pickStrategy($arguments->algorithm(), new StrategyConfiguration($arguments));
×
81
        } catch (InvalidStrategyException $exception) {
×
82
            print $exception->getMessage() . PHP_EOL;
×
83

84
            return 1;
×
85
        }
86

87
        $timer = new Timer();
×
88
        $timer->start();
×
89

90
        try {
NEW
91
            $clones = (new Detector($strategy, true))->copyPasteDetection($files);
×
92
        } catch (ProcessingResultException $exception) {
×
93
            print 'Processing error: ' . $exception->getMessage() . PHP_EOL;
×
94

95
            return 1;
×
96
        }
97

98
        (new Text())->printResult($clones, $arguments->verbose());
×
99

100
        if ($arguments->pmdCpdXmlLogfile()) {
×
101
            try {
102
                (new PMD($arguments->pmdCpdXmlLogfile()))->processClones($clones);
×
103
            } catch (LoggerException $exception) {
×
104
                print 'Logger error: ' . $exception->getMessage() . PHP_EOL;
×
105

106
                return 1;
×
107
            }
108
        }
109

110
        print (new ResourceUsageFormatter())->resourceUsage($timer->stop()) . PHP_EOL;
×
111

112
        return count($clones) > 0 ? 1 : 0;
×
113
    }
114

115
    private function printVersion(): void
116
    {
117
        /** @var non-empty-string $path */
118
        $path = dirname(__DIR__);
×
NEW
119
        printf('%s %s', 'Copy/Paste Detector', (new Version(self::VERSION, $path))->asString());
×
120
    }
121

122
    /**
123
     * @throws InvalidStrategyException
124
     */
125
    private function pickStrategy(string $algorithm, StrategyConfiguration $config): AbstractStrategy
126
    {
127
        return match ($algorithm) {
×
128
            ArgumentsBuilder::ALGORITHM_RABIN_KARP_NAME => new DefaultStrategy($config),
×
129
            ArgumentsBuilder::ALGORITHM_SUFFIX_TREE_NAME => new SuffixTreeStrategy($config),
×
130
            default => throw new InvalidStrategyException('Unsupported algorithm: ' . $algorithm),
×
131
        };
×
132
    }
133

134
    private function help(): void
135
    {
136
        print <<<'EOT'
×
137
Usage:
138
  phpcpd [options] <directory>
139

140
Options for selecting files:
141

142
  --suffix <suffix> Include files with names ending on <suffix> (default: .php; can be given multiple times)
143
  --exclude <path>  Exclude files with <path> in their path (can be given multiple times)
144

145
Options for analysing files:
146

147
  --algorithm <name>  Select which algorithm to use ('rabin-karp' (default) or 'suffix-tree' (experimental))
148
  --fuzzy             Fuzz variable names
149
  --min-lines <N>     Minimum number of identical lines (default: 5)
150
  --min-tokens <N>    Minimum number of identical tokens (default: 70)
151
  --edit-distance <N> Distance in number of edits between two clones (only for suffix-tree; default: 0)
152
  --head-equality <N> Minimum equality at start of clone (only for suffix-tree; default 10)
153
  --verbose           Print results details
154

155
Options for report generation:
156

157
  --log-pmd <file>  Write log in PMD-CPD XML format to <file>
158

159
General options:
160

161
  --version         Display version
162
  --help            Display help
163

164
EOT;
×
165
    }
166
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc