DirCommand   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 100%

Importance

Changes 22
Bugs 2 Features 4
Metric Value
wmc 30
c 22
b 2
f 4
lcom 1
cbo 13
dl 0
loc 214
ccs 144
cts 144
cp 1
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 19 1
C execute() 0 51 9
C renderResultCollectionByRequiredVersion() 0 59 10
C renderResultCollectionByName() 0 65 10
1
<?php
2
/**
3
 * DirCommand.php
4
 *
5
 * MIT LICENSE
6
 *
7
 * LICENSE: This source file is subject to the MIT license.
8
 * A copy of the licenses text was distributed alongside this
9
 * file (usually the repository or package root). The text can also
10
 * be obtained through one of the following sources:
11
 * * http://opensource.org/licenses/MIT
12
 * * https://github.com/suralc/pvra/blob/master/LICENSE
13
 *
14
 * @author     suralc <[email protected]>
15
 * @license    http://opensource.org/licenses/MIT  MIT
16
 */
17
namespace Pvra\Console\Commands;
18
19
20
use Pvra\AnalysisResult;
21
use Pvra\Console\Services\FileFinderBuilder;
22
use Pvra\Result\Collection;
23
use Pvra\Result\MessageFormatter;
24
use Pvra\Result\Reasoning;
25
use RuntimeException;
26
use Symfony\Component\Console\Helper\Table;
27
use Symfony\Component\Console\Helper\TableSeparator;
28
use Symfony\Component\Console\Input\InputArgument;
29
use Symfony\Component\Console\Input\InputInterface;
30
use Symfony\Component\Console\Input\InputOption;
31
use Symfony\Component\Console\Output\OutputInterface;
32
use Symfony\Component\Console\Question\ConfirmationQuestion;
33
34
/**
35
 * Class DirCommand
36
 *
37
 * @package Pvra\Console\Commands
38
 */
39
class DirCommand extends PvraBaseCommand
40
{
41
    const GROUP_BY_NAME = 'name',
42
        GROUP_BY_VERSION = 'version';
43
44
    /**
45
     * @inheritdoc
46
     */
47 64
    protected function configure()
48
    {
49 32
        $this
50 64
            ->setName('analyse:dir')
51 64
            ->setDescription('Iterates over a directory and runs the specified analysers.');
52
53 64
        parent::configure();
54
55 32
        $this
56 64
            ->addOption('recursive', 'r', InputOption::VALUE_NONE, 'Iterate recursive over directory')
57 64
            ->addOption('groupBy', 'g', InputOption::VALUE_REQUIRED, 'Group output by name or required version.',
58 64
                self::GROUP_BY_NAME)
59 64
            ->addOption('sortBy', 'o', InputOption::VALUE_REQUIRED,
60 64
                'Sort order of remaining files. Only takes effect while using --groupBy=n[ame]',
61 64
                FileFinderBuilder::SORT_BY_NAME)
62 64
            ->addOption('listFilesOnly', null, InputOption::VALUE_NONE,
63 64
                'Only list matched files and do not run analysis.')
64 64
            ->addArgument('filters', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Filter', ['name:*.php']);
65 64
    }
66
67
    /**
68
     * @inheritdoc
69
     */
70 22
    protected function execute(InputInterface $input, OutputInterface $output)
71
    {
72 22
        $dir = $input->getArgument('target');
73
74 22
        $files = (new FileFinderBuilder($dir))
75 22
            ->isRecursive($input->getOption('recursive'))
76 22
            ->sortBy($input->getOption('sortBy'))
77 22
            ->withFilters($input->getArgument('filters'))
78 22
            ->getFinder();
79
80 22
        if ($files->count() === 0) {
81 2
            $output->writeln('<error>No files processed!</error>');
82 2
            return;
83
        }
84
85 20
        if ($input->getOption('listFilesOnly')) {
86 2
            $output->writeln($files->count() . ' files to process.');
87
            /** @var \SplFileInfo $file */
88 2
            foreach ($files as $file) {
89 2
                $output->writeln($this->formatOutputPath($file->getRealPath()));
90 1
            }
91 2
            return;
92
        }
93
94
95 18
        $results = new Collection();
96 18
        $messageFormatter = new MessageFormatter($this->createMessageLocatorInstance($input), false);
97
98
        /** @var \SplFileInfo $file */
99 18
        foreach ($files as $file) {
100 18
            if ($file->isFile()) {
101 18
                $result = (new AnalysisResult())->setMsgFormatter($messageFormatter);
102 18
                $req = $this->createFileAnalyserInstance($file->getPathname());
103 18
                $req->setResultInstance($result);
104 18
                $req->attachRequirementVisitors($this->createNodeWalkerInstances($input->getOption('libraryDataSource')));
105 18
                $results->add($req->run());
106 9
            }
107 9
        }
108
109 18
        if ($input->getOption('groupBy') === self::GROUP_BY_NAME) {
110 14
            $this->renderResultCollectionByName($results, $output, $input);
111 11
        } elseif ($input->getOption('groupBy') === self::GROUP_BY_VERSION) {
112 2
            $this->renderResultCollectionByRequiredVersion($results, $output, $input);
113 1
        } else {
114 2
            throw new \InvalidArgumentException('The value given to the groupBy option is not supported.');
115
        }
116
117 16
        if ($file = $input->getOption('saveAsFile')) {
118 6
            $this->writeToFile($file, $input->getOption('saveFormat'), $results, $output);
119 3
        }
120 16
    }
121
122 14
    protected function renderResultCollectionByName(Collection $results, OutputInterface $out, InputInterface $in)
123
    {
124 14
        $highestRequirement = $results->getHighestDemandingResult();
125
126 14
        if ($highestRequirement === null) {
127
            // @codeCoverageIgnoreStart
128
            throw new RuntimeException('Detection of requirements failed. Unknown error.');
129
            // @codeCoverageIgnoreEnd
130
        }
131 14
        $out->writeln('Highest required version: ' . $highestRequirement->getRequiredVersion());
132 14
        $out->writeln(sprintf('Required because %s uses the following features:',
133 14
            $highestRequirement->getAnalysisTargetId()));
134
135 14
        if ($highestRequirement->count() !== 0) {
136 12
            $tableData = [];
137
            // order by version->descending. Might want to implement ordering by line later.
138 12
            foreach (array_reverse($highestRequirement->getRequirements()) as $version => $reasons) {
139 12
                foreach ($reasons as $reason) {
140 12
                    $tableData[] = [$reason['version'], $reason['msg'], $reason['line']];
141 6
                }
142 6
            }
143
144 12
            (new Table($out))
145 12
                ->setHeaders(['Version', 'Message', 'Line'])
146 12
                ->setRows($tableData)
147 12
                ->render();
148 6
        } else {
149 2
            $out->writeln("\t<info>No requirements beyond the default version (5.3) could be found.</info>");
150
        }
151
152 14
        if ($results->count() > 1) {
153 6
            $helper = $this->getHelper('question');
154 6
            $question = new ConfirmationQuestion('Continue with showing remaining ' . ($results->count() - 1) . ' results? [Y/n] ',
155 6
                true);
156
157 6
            if (!$helper->ask($in, $out, $question)) {
158 2
                return;
159
            }
160 4
            $out->writeln('<info>Other results(' . ($results->count() - 1) . '):</info>');
161
            /** @var AnalysisResult $result */
162 4
            foreach ($results as $result) {
163 4
                if ($result->getAnalysisTargetId() === $highestRequirement->getAnalysisTargetId()) {
164 4
                    continue;
165
                }
166 4
                $out->write([
167 4
                    'The file "',
168 4
                    $this->formatOutputPath($result->getAnalysisTargetId()),
169 4
                    '" requires PHP ',
170 4
                    $result->getRequiredVersion(),
171 4
                    ' for the following reasons:',
172 4
                    PHP_EOL,
173 2
                ]);
174 4
                $tableData = [];
175
                /** @var $reason Reasoning */
176 4
                foreach ($result->getRequirementIterator() as $reason) {
177 4
                    $tableData[] = [$reason['version'], $reason['msg'], $reason['line']];
178 2
                }
179
180 4
                (new Table($out))
181 4
                    ->setHeaders(['Version', 'Message', 'Line'])
182 4
                    ->setRows($tableData)
183 4
                    ->render();
184 2
            }
185 2
        }
186 12
    }
187
188
    /**
189
     * @param \Pvra\Result\Collection $results
190
     * @param \Symfony\Component\Console\Output\OutputInterface $out
191
     * @param \Symfony\Component\Console\Input\InputInterface $in
192
     */
193 2
    protected function renderResultCollectionByRequiredVersion(
194
        Collection $results,
195
        OutputInterface $out,
196
        InputInterface $in
197
    ) {
198 2
        $highestRequirement = $results->getHighestDemandingResult();
199
200 2
        if ($highestRequirement === null) {
201
            // @codeCoverageIgnoreStart
202
            throw new RuntimeException('Detection of requirements failed. Unknown error.');
203
            // @codeCoverageIgnoreEnd
204
        }
205
206 2
        $out->write([
207 2
            'Highest required version is PHP ',
208 2
            $highestRequirement->getRequiredVersion(),
209 2
            ' in ',
210 2
            $highestRequirement->getAnalysisTargetId(),
211 2
            $results->count() > 1 ? ' and others' : '',
212 2
            PHP_EOL,
213 1
        ]);
214 2
        $out->writeln('');
215
216 2
        $usedVersions = [];
217
        /** @var AnalysisResult $result */
218 2
        foreach ($results as $result) {
219 2
            $versions = array_keys($result->getRequirements());
220 2
            foreach ($versions as $version) {
221 2
                $usedVersions[ $version ] = $version;
222 1
            }
223 1
        }
224
225 2
        usort($usedVersions, function ($a, $b) {
226 2
            return version_compare($b, $a);
227 2
        });
228
229 2
        $table = new Table($out);
230 2
        $table->setHeaders(['Version', 'Message', 'Position']);
231
232 2
        foreach ($usedVersions as $index => $version) {
233
            /** @var AnalysisResult $result */
234 2
            foreach ($results as $result) {
235 2
                $selectedResults = $result->getRequirementInfo($version);
236 2
                if (!empty($selectedResults)) {
237 2
                    foreach ($selectedResults as $reason) {
238 2
                        $table->addRow([
239 2
                            $version,
240 2
                            $reason['msg'],
241 2
                            $this->formatOutputPath($reason['targetId']) . ':' . $reason['line'],
242 1
                        ]);
243 1
                    }
244 1
                }
245 1
            }
246 2
            if (isset($usedVersions[ $index + 1 ])) { // there is a row after this one
247 2
                $table->addRow(new TableSeparator());
248 1
            }
249 1
        }
250 2
        $table->render();
251 2
    }
252
}
253