Completed
Pull Request — master (#51)
by Povilas
02:03
created

Command::createFinder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
1
<?php
2
3
namespace Povils\PHPMND\Console;
4
5
use Povils\PHPMND\Detector;
6
use Povils\PHPMND\ExtensionResolver;
7
use Povils\PHPMND\FileReportList;
8
use Povils\PHPMND\HintList;
9
use Povils\PHPMND\PHPFinder;
10
use Povils\PHPMND\Printer;
11
use Symfony\Component\Console\Command\Command as BaseCommand;
12
use Symfony\Component\Console\Helper\ProgressBar;
13
use Symfony\Component\Console\Input\InputArgument;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
18
/**
19
 * Class Command
20
 *
21
 * @package Povils\PHPMND\Console
22
 */
23
class Command extends BaseCommand
24
{
25
    const EXIT_CODE_SUCCESS = 0;
26
    const EXIT_CODE_FAILURE = 1;
27
28
    /**
29
     * @inheritdoc
30
     */
31
    protected function configure()
32
    {
33
        $this
34
            ->setName('phpmnd')
35
            ->setDefinition(
36
                [
37
                    new InputArgument(
38
                        'directory',
39
                        InputArgument::REQUIRED,
40
                        'Directory to analyze'
41
                    )
42
                ]
43
            )
44
            ->addOption(
45
                'extensions',
46
                null,
47
                InputOption::VALUE_REQUIRED,
48
                'A comma-separated list of extensions',
49
                []
50
            )
51
            ->addOption(
52
                'ignore-numbers',
53
                null,
54
                InputOption::VALUE_REQUIRED,
55
                'A comma-separated list of numbers to ignore',
56
                [0, 1]
57
            )
58
            ->addOption(
59
                'ignore-funcs',
60
                null,
61
                InputOption::VALUE_REQUIRED,
62
                'A comma-separated list of functions to ignore when using "argument" extension',
63
                []
64
            )
65
            ->addOption(
66
                'exclude',
67
                null,
68
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
69
                'Exclude a directory from code analysis (must be relative to source)'
70
            )
71
            ->addOption(
72
                'exclude-path',
73
                null,
74
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
75
                'Exclude a path from code analysis (must be relative to source)'
76
            )
77
            ->addOption(
78
                'exclude-file',
79
                null,
80
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
81
                'Exclude a file from code analysis (must be relative to source)'
82
            )
83
            ->addOption(
84
                'suffixes',
85
                null,
86
                InputOption::VALUE_REQUIRED,
87
                'Comma-separated string of valid source code filename extensions',
88
                'php'
89
            )
90
            ->addOption(
91
                'progress',
92
                null,
93
                InputOption::VALUE_NONE,
94
                'Show progress bar'
95
            )
96
            ->addOption(
97
                'hint',
98
                null,
99
                InputOption::VALUE_NONE,
100
                'Suggest replacements for magic numbers'
101
            )
102
            ->addOption(
103
                'non-zero-exit-on-violation',
104
                null,
105
                InputOption::VALUE_NONE,
106
                'Return a non zero exit code when there are magic numbers'
107
            )
108
            ->addOption(
109
                'strings',
110
                null,
111
                InputOption::VALUE_NONE,
112
                'Include strings literal search in code analysis'
113
            )
114
            ->addOption(
115
                'ignore-strings',
116
                null,
117
                InputOption::VALUE_REQUIRED,
118
                'A comma-separated list of strings to ignore when using "strings" option',
119
                []
120
            );
121
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126
    protected function execute(InputInterface $input, OutputInterface $output)
127
    {
128
        $finder = $this->createFinder($input);
129
130
        if (0 === $finder->count()) {
131
            $output->writeln('No files found to scan');
132
            return self::EXIT_CODE_SUCCESS;
133
        }
134
135
        $progressBar = null;
136
        if ($input->getOption('progress')) {
137
            $progressBar = new ProgressBar($output, $finder->count());
138
            $progressBar->start();
139
        }
140
141
        $hintList = new HintList;
142
        $detector = new Detector($this->createOption($input), $hintList);
143
144
        $fileReportList = new FileReportList();
145
        $printer = new Printer();
146
        foreach ($finder as $file) {
147
            try {
148
                $fileReport = $detector->detect($file);
149
                if ($fileReport->hasMagicNumbers()) {
150
                    $fileReportList->addFileReport($fileReport);
151
                }
152
            } catch (\Exception $e) {
153
                $output->writeln($e->getMessage());
154
            }
155
156
            if ($input->getOption('progress')) {
157
                $progressBar->advance();
158
            }
159
        }
160
161
        if ($input->getOption('progress')) {
162
            $progressBar->finish();
163
        }
164
165
        if ($output->getVerbosity() !== OutputInterface::VERBOSITY_QUIET) {
166
            $output->writeln('');
167
            $printer->printData($output, $fileReportList, $hintList);
168
            $output->writeln('<info>' . \PHP_Timer::resourceUsage() . '</info>');
169
        }
170
171
        if ($input->getOption('non-zero-exit-on-violation') && $fileReportList->hasMagicNumbers()) {
172
            return self::EXIT_CODE_FAILURE;
173
        }
174
175
        return self::EXIT_CODE_SUCCESS;
176
    }
177
178
    /**
179
     * @param InputInterface $input
180
     * @return Option
181
     * @throws \Exception
182
     */
183
    private function createOption(InputInterface $input)
184
    {
185
        $option = new Option;
186
        $option->setIgnoreNumbers(array_map([$this, 'castToNumber'], $this->getCSVOption($input, 'ignore-numbers')));
187
        $option->setIgnoreFuncs($this->getCSVOption($input, 'ignore-funcs'));
188
        $option->setIncludeStrings($input->getOption('strings'));
189
        $option->setIgnoreStrings($input->getOption('ignore-strings'));
190
        $option->setGiveHint($input->getOption('hint'));
191
        $option->setExtensions(
192
            (new ExtensionResolver())->resolve($this->getCSVOption($input, 'extensions'))
193
        );
194
195
        return $option;
196
    }
197
198
    /**
199
     * @param InputInterface $input
200
     * @param string $option
201
     *
202
     * @return array
203
     */
204
    private function getCSVOption(InputInterface $input, $option)
205
    {
206
        $result = $input->getOption($option);
207
        if (false === is_array($result)) {
208
            return explode(',', $result);
209
        }
210
211
        return $result;
212
    }
213
214
    /**
215
     * @param InputInterface $input
216
     *
217
     * @return PHPFinder
218
     */
219
    protected function createFinder(InputInterface $input)
220
    {
221
        return new PHPFinder(
222
            $input->getArgument('directory'),
223
            $input->getOption('exclude'),
224
            $input->getOption('exclude-path'),
225
            $input->getOption('exclude-file'),
226
            $this->getCSVOption($input, 'suffixes')
227
        );
228
    }
229
230
    /**
231
     * @param string $value
232
     *
233
     * @return int|float|string
234
     */
235
    private function castToNumber($value)
236
    {
237
        if (is_numeric($value)) {
238
            $value += 0; // '2' -> (int) 2, '2.' -> (float) 2.0
239
        }
240
241
        return $value;
242
    }
243
}
244