1 | <?php |
||
25 | class Command extends BaseCommand |
||
26 | { |
||
27 | const EXIT_CODE_SUCCESS = 0; |
||
28 | const EXIT_CODE_FAILURE = 1; |
||
29 | |||
30 | /** |
||
31 | * @var Timer |
||
32 | */ |
||
33 | private $timer; |
||
34 | |||
35 | protected function configure(): void |
||
36 | { |
||
37 | $this |
||
38 | ->setName('phpmnd') |
||
39 | ->setDefinition( |
||
40 | [ |
||
41 | new InputArgument( |
||
42 | 'directories', |
||
43 | InputArgument::REQUIRED + InputArgument::IS_ARRAY, |
||
44 | 'One or more files and/or directories to analyze' |
||
45 | ), |
||
46 | ] |
||
47 | ) |
||
48 | ->addOption( |
||
49 | 'extensions', |
||
50 | null, |
||
51 | InputOption::VALUE_REQUIRED, |
||
52 | 'A comma-separated list of extensions' |
||
53 | ) |
||
54 | ->addOption( |
||
55 | 'ignore-numbers', |
||
56 | null, |
||
57 | InputOption::VALUE_REQUIRED, |
||
58 | 'A comma-separated list of numbers to ignore', |
||
59 | '0, 1' |
||
60 | ) |
||
61 | ->addOption( |
||
62 | 'ignore-funcs', |
||
63 | null, |
||
64 | InputOption::VALUE_REQUIRED, |
||
65 | 'A comma-separated list of functions to ignore when using "argument" extension' |
||
66 | ) |
||
67 | ->addOption( |
||
68 | 'exclude', |
||
69 | null, |
||
70 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, |
||
71 | 'Exclude a directory from code analysis (must be relative to source)' |
||
72 | ) |
||
73 | ->addOption( |
||
74 | 'exclude-path', |
||
75 | null, |
||
76 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, |
||
77 | 'Exclude a path from code analysis (must be relative to source)' |
||
78 | ) |
||
79 | ->addOption( |
||
80 | 'exclude-file', |
||
81 | null, |
||
82 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, |
||
83 | 'Exclude a file from code analysis (must be relative to source)' |
||
84 | ) |
||
85 | ->addOption( |
||
86 | 'suffixes', |
||
87 | null, |
||
88 | InputOption::VALUE_REQUIRED, |
||
89 | 'Comma-separated string of valid source code filename extensions', |
||
90 | 'php' |
||
91 | ) |
||
92 | ->addOption( |
||
93 | 'progress', |
||
94 | null, |
||
95 | InputOption::VALUE_NONE, |
||
96 | 'Show progress bar' |
||
97 | ) |
||
98 | ->addOption( |
||
99 | 'hint', |
||
100 | null, |
||
101 | InputOption::VALUE_NONE, |
||
102 | 'Suggest replacements for magic numbers' |
||
103 | ) |
||
104 | ->addOption( |
||
105 | 'non-zero-exit-on-violation', |
||
106 | null, |
||
107 | InputOption::VALUE_NONE, |
||
108 | 'Return a non zero exit code when there are magic numbers' |
||
109 | ) |
||
110 | ->addOption( |
||
111 | 'strings', |
||
112 | null, |
||
113 | InputOption::VALUE_NONE, |
||
114 | 'Include strings literal search in code analysis' |
||
115 | ) |
||
116 | ->addOption( |
||
117 | 'ignore-strings', |
||
118 | null, |
||
119 | InputOption::VALUE_REQUIRED, |
||
120 | 'A comma-separated list of strings to ignore when using "strings" option' |
||
121 | ) |
||
122 | ->addOption( |
||
123 | 'include-numeric-string', |
||
124 | null, |
||
125 | InputOption::VALUE_NONE, |
||
126 | 'Include strings which are numeric' |
||
127 | ) |
||
128 | ->addOption( |
||
129 | 'allow-array-mapping', |
||
130 | null, |
||
131 | InputOption::VALUE_NONE, |
||
132 | 'Allow array mapping (key as strings) when using "array" extension.' |
||
133 | ) |
||
134 | ->addOption( |
||
135 | 'xml-output', |
||
136 | null, |
||
137 | InputOption::VALUE_REQUIRED, |
||
138 | 'Generate an XML output to the specified path' |
||
139 | ) |
||
140 | ->addOption( |
||
141 | 'whitelist', |
||
142 | null, |
||
143 | InputOption::VALUE_REQUIRED, |
||
144 | 'Link to a file containing filenames to search', |
||
145 | '' |
||
146 | ) |
||
147 | ; |
||
148 | } |
||
149 | |||
150 | protected function execute(InputInterface $input, OutputInterface $output): int |
||
151 | { |
||
152 | $this->startTimer(); |
||
153 | $finder = $this->createFinder($input); |
||
154 | |||
155 | if (0 === $finder->count()) { |
||
156 | $output->writeln('No files found to scan'); |
||
157 | return self::EXIT_CODE_SUCCESS; |
||
158 | } |
||
159 | |||
160 | $progressBar = null; |
||
161 | if ($input->getOption('progress')) { |
||
162 | $progressBar = new ProgressBar($output, $finder->count()); |
||
163 | $progressBar->start(); |
||
164 | } |
||
165 | |||
166 | $hintList = new HintList; |
||
167 | $detector = new Detector($this->createOption($input), $hintList); |
||
168 | |||
169 | $fileReportList = new FileReportList(); |
||
170 | $printer = new Printer\Console(); |
||
171 | $whitelist = $this->getFileOption($input->getOption('whitelist')); |
||
172 | |||
173 | foreach ($finder as $file) { |
||
174 | if (count($whitelist) > 0 && !in_array($file->getRelativePathname(), $whitelist)) { |
||
175 | continue; |
||
176 | } |
||
177 | |||
178 | try { |
||
179 | $fileReport = $detector->detect($file); |
||
180 | if ($fileReport->hasMagicNumbers()) { |
||
181 | $fileReportList->addFileReport($fileReport); |
||
182 | } |
||
183 | } catch (\Exception $e) { |
||
184 | $output->writeln($e->getMessage()); |
||
185 | } |
||
186 | |||
187 | if ($input->getOption('progress')) { |
||
188 | $progressBar->advance(); |
||
189 | } |
||
190 | } |
||
191 | |||
192 | if ($input->getOption('progress')) { |
||
193 | $progressBar->finish(); |
||
194 | } |
||
195 | |||
196 | if ($input->getOption('xml-output')) { |
||
197 | $xmlOutput = new Printer\Xml($input->getOption('xml-output')); |
||
198 | $xmlOutput->printData($output, $fileReportList, $hintList); |
||
199 | } |
||
200 | |||
201 | if ($output->getVerbosity() !== OutputInterface::VERBOSITY_QUIET) { |
||
202 | $output->writeln(''); |
||
203 | $printer->printData($output, $fileReportList, $hintList); |
||
204 | $output->writeln('<info>' . $this->getResourceUsage() . '</info>'); |
||
205 | } |
||
206 | |||
207 | if ($input->getOption('non-zero-exit-on-violation') && $fileReportList->hasMagicNumbers()) { |
||
208 | return self::EXIT_CODE_FAILURE; |
||
209 | } |
||
210 | return self::EXIT_CODE_SUCCESS; |
||
211 | } |
||
212 | |||
213 | private function createOption(InputInterface $input): Option |
||
214 | { |
||
215 | $option = new Option; |
||
216 | $option->setIgnoreNumbers(array_map([$this, 'castToNumber'], $this->getCSVOption($input, 'ignore-numbers'))); |
||
217 | $option->setIgnoreFuncs($this->getCSVOption($input, 'ignore-funcs')); |
||
218 | $option->setIncludeStrings($input->getOption('strings')); |
||
219 | $option->setIncludeNumericStrings($input->getOption('include-numeric-string')); |
||
220 | $option->setIgnoreStrings($this->getCSVOption($input, 'ignore-strings')); |
||
221 | $option->setAllowArrayMapping($input->getOption('allow-array-mapping')); |
||
222 | $option->setGiveHint($input->getOption('hint')); |
||
223 | $option->setExtensions( |
||
224 | (new ExtensionResolver())->resolve($this->getCSVOption($input, 'extensions')) |
||
225 | ); |
||
226 | |||
227 | return $option; |
||
228 | } |
||
229 | |||
230 | private function getCSVOption(InputInterface $input, string $option): array |
||
248 | |||
249 | protected function createFinder(InputInterface $input): PHPFinder |
||
250 | { |
||
251 | return new PHPFinder( |
||
259 | |||
260 | private function castToNumber(string $value) |
||
268 | |||
269 | private function getFileOption($filename) |
||
279 | |||
280 | private function convertFileDescriptorLink($path) |
||
288 | |||
289 | private function startTimer() |
||
296 | |||
297 | private function getResourceUsage() |
||
307 | } |
||
308 |
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.