Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.25% covered (success)
98.25%
56 / 57
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetricsCommand
98.25% covered (success)
98.25%
56 / 57
66.67% covered (warning)
66.67%
2 / 3
8
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configure
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
 execute
96.97% covered (success)
96.97%
32 / 33
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Console\Command;
21
22use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
23use Composer\Command\BaseCommand;
24use FastForward\DevTools\Console\Input\HasJsonOption;
25use FastForward\DevTools\Process\ProcessBuilderInterface;
26use FastForward\DevTools\Process\ProcessQueueInterface;
27use FastForward\DevTools\Path\ManagedWorkspace;
28use Psr\Log\LoggerInterface;
29use Symfony\Component\Console\Attribute\AsCommand;
30use Symfony\Component\Console\Input\InputInterface;
31use Symfony\Component\Console\Input\InputOption;
32use Symfony\Component\Console\Output\BufferedOutput;
33use Symfony\Component\Console\Output\OutputInterface;
34
35use function rtrim;
36
37#[AsCommand(
38    name: 'metrics',
39    description: 'Analyzes code metrics with PhpMetrics.',
40    help: 'This command runs PhpMetrics to analyze the current working directory.',
41)]
42 class MetricsCommand extends BaseCommand implements LoggerAwareCommandInterface
43{
44    use HasJsonOption;
45    use LogsCommandResults;
46
47    /**
48     * @var string the bundled PhpMetrics binary path relative to the consumer root
49     */
50    private const string BINARY = 'vendor/bin/phpmetrics';
51
52    /**
53     * @var int the PHP error reporting mask that suppresses deprecations emitted by PhpMetrics internals
54     */
55    private const int PHP_ERROR_REPORTING = \E_ALL & ~\E_DEPRECATED;
56
57    /**
58     * @param ProcessBuilderInterface $processBuilder the builder used to assemble the PhpMetrics process
59     * @param ProcessQueueInterface $processQueue the queue used to execute the PhpMetrics process
60     * @param LoggerInterface $logger the output-aware logger
61     */
62    public function __construct(
63        private  ProcessBuilderInterface $processBuilder,
64        private  ProcessQueueInterface $processQueue,
65        private  LoggerInterface $logger,
66    ) {
67        parent::__construct();
68    }
69
70    /**
71     * @return void
72     */
73    protected function configure(): void
74    {
75        $this->addJsonOption()
76            ->addOption(
77                name: 'progress',
78                mode: InputOption::VALUE_NONE,
79                description: 'Whether to enable progress output from PhpMetrics.',
80            )
81            ->addOption(
82                name: 'exclude',
83                mode: InputOption::VALUE_OPTIONAL,
84                description: 'Comma-separated directories that SHOULD be excluded from analysis.',
85                default: 'vendor,test,tests,tmp,cache,spec,build,.dev-tools,backup,resources',
86            )
87            ->addOption(
88                name: 'target',
89                mode: InputOption::VALUE_OPTIONAL,
90                description: 'Target directory for the generated metrics reports.',
91                default: ManagedWorkspace::getOutputDirectory(ManagedWorkspace::METRICS),
92            )
93            ->addOption(
94                name: 'junit',
95                mode: InputOption::VALUE_OPTIONAL,
96                description: 'Optional target file for the generated JUnit XML report.',
97            );
98    }
99
100    /**
101     * @param InputInterface $input the runtime command input
102     * @param OutputInterface $output the console output stream
103     *
104     * @return int the command execution status code
105     */
106    protected function execute(InputInterface $input, OutputInterface $output): int
107    {
108        $jsonOutput = $this->isJsonOutput($input);
109        $processOutput = $jsonOutput ? new BufferedOutput() : $output;
110        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
111
112        $target = rtrim((string) $input->getOption('target'), '/');
113        $exclude = (string) $input->getOption('exclude');
114        $junit = $input->getOption('junit');
115
116        $this->logger->info('Running code metrics analysis...', [
117            'input' => $input,
118        ]);
119
120        $processBuilder = $this->processBuilder
121            ->withArgument('--ansi')
122            ->withArgument('--git', 'git')
123            ->withArgument('--exclude', $exclude)
124            ->withArgument('--report-html', $target)
125            ->withArgument('--report-json', $target . '/report.json')
126            ->withArgument('--report-summary-json', $target . '/report-summary.json');
127
128        if (! $progress) {
129            $processBuilder = $processBuilder->withArgument('--quiet');
130        }
131
132        if (null !== $junit) {
133            $processBuilder = $processBuilder->withArgument('--junit', $junit);
134        }
135
136        $this->processQueue->add(
137            $processBuilder
138                ->withArgument('.')
139                ->build([\PHP_BINARY, '-derror_reporting=' . self::PHP_ERROR_REPORTING, self::BINARY])
140        );
141
142        $result = $this->processQueue->run($processOutput);
143
144        if (self::SUCCESS === $result) {
145            return $this->success('Code metrics analysis completed successfully.', $input, [
146                'output' => $processOutput,
147            ]);
148        }
149
150        return $this->failure('Code metrics analysis failed.', $input, [
151            'output' => $processOutput,
152        ]);
153    }
154}