Completed
Push — master ( 8ccc11...1c9ae4 )
by Povilas
9s
created

Command::castToNumber()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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