RunCommand::checkErrors()   B
last analyzed

Complexity

Conditions 11
Paths 17

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 15
nc 17
nop 1
dl 0
loc 26
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * RunCommand
5
 *
6
 * PHP Version 5.3.0
7
 *
8
 * Copyright (c) 2007-2014, Mayflower GmbH
9
 * All rights reserved.
10
 *
11
 * Redistribution and use in source and binary forms, with or without
12
 * modification, are permitted provided that the following conditions
13
 * are met:
14
 *
15
 *   * Redistributions of source code must retain the above copyright
16
 *     notice, this list of conditions and the following disclaimer.
17
 *
18
 *   * Redistributions in binary form must reproduce the above copyright
19
 *     notice, this list of conditions and the following disclaimer in
20
 *     the documentation and/or other materials provided with the
21
 *     distribution.
22
 *
23
 *   * Neither the name of Mayflower GmbH nor the names of his
24
 *     contributors may be used to endorse or promote products derived
25
 *     from this software without specific prior written permission.
26
 *
27
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
30
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
31
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
33
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
37
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
 * POSSIBILITY OF SUCH DAMAGE.
39
 *
40
 * @category PHP_CodeBrowser
41
 *
42
 * @author Robin Gloster <[email protected]>
43
 *
44
 * @copyright 2007-2010 Mayflower GmbH
45
 *
46
 * @license http://www.opensource.org/licenses/bsd-license.php  BSD License
47
 *
48
 * @version SVN: $Id$
49
 *
50
 * @link http://www.phpunit.de/
51
 *
52
 * @since File available since 1.1
53
 */
54
55
namespace PHPCodeBrowser\Command;
56
57
use Exception;
58
use Monolog\Handler\NullHandler;
59
use Monolog\Logger;
60
use PHPCodeBrowser\CLIController;
61
use PHPCodeBrowser\Helper\IOHelper;
62
use PHPCodeBrowser\Plugins\ErrorCPD;
63
use PHPCodeBrowser\Plugins\ErrorCRAP;
64
use PHPCodeBrowser\Plugins\ErrorCheckstyle;
65
use PHPCodeBrowser\Plugins\ErrorCoverage;
66
use PHPCodeBrowser\Plugins\ErrorPMD;
67
use PHPCodeBrowser\Plugins\ErrorPadawan;
68
use Symfony\Component\Console\Command\Command;
69
use Symfony\Component\Console\Input\InputInterface;
70
use Symfony\Component\Console\Input\InputOption;
71
use Symfony\Component\Console\Output\OutputInterface;
72
73
/**
74
 * Class RunCommand
75
 */
76
class RunCommand extends Command
77
{
78
    /**
79
     *
80
     */
81
    protected function configure(): void
82
    {
83
        $plugins = \array_map(
84
            static function ($class) {
85
                return '"'.\substr($class, \strlen('Error')).'"';
86
            },
87
            $this->getAvailablePlugins()
88
        );
89
90
        $this->setName('phpcb')
91
            ->setHelp(
92
                'A Code browser for PHP files with syntax highlighting and colored error-sections found by quality assurance tools like PHPUnit, PHPMD or PHP_CodeSniffer.'
93
            )->addOption(
94
                'log',
95
                'l',
96
                InputOption::VALUE_REQUIRED,
97
                'The path to the xml log files, e.g. generated from PHPUnit. Either this or --source must be given'
98
            )->addOption(
99
                'extensions',
100
                'S',
101
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
102
                'PHP file extensions to include. Can be given multiple times'
103
            )->addOption(
104
                'output',
105
                'o',
106
                InputOption::VALUE_REQUIRED,
107
                'Path to the output folder where generated files should be stored'
108
            )->addOption(
109
                'source',
110
                's',
111
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
112
                'Path to the project source code. Can either be a directory or a single file. Parse complete source directory if set, else only files found in logs. Either this or --log must be given. Can be given multiple times'
113
            )->addOption(
114
                'ignore',
115
                'i',
116
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
117
                'Files or directories that will be ignored during the parsing process. Can be given multiple times'
118
            )->addOption(
119
                'exclude',
120
                'e',
121
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
122
                'Excludes all files matching the given glob pattern. This is done after pulling the files in the source dir in if one is given. Can be given multiple times. Note that the match is run against absolute file names'
123
            )->addOption(
124
                'excludePCRE',
125
                'E',
126
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
127
                'Works like -e but takes PCRE instead of glob patterns'
128
            )->addOption(
129
                'debugExcludes',
130
                null,
131
                InputOption::VALUE_NONE,
132
                'Print which files are excluded by which expressions and patterns'
133
            )->addOption(
134
                'excludeOK',
135
                null,
136
                InputOption::VALUE_NONE,
137
                'Exclude files with no issues from the report'
138
            )->addOption(
139
                'disablePlugin',
140
                null,
141
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
142
                'Disable single Plugins. Can be one of '.\implode(', ', $plugins)
143
            )->addOption(
144
                'crapThreshold',
145
                null,
146
                InputOption::VALUE_REQUIRED,
147
                'The minimum value for CRAP errors to be recognized. Defaults to 0. Regardless of this setting, values below 30 will be considered notices, those above warnings'
148
            );
149
    }
150
151
    /**
152
     * Executes the current command.
153
     *
154
     * @param InputInterface  $input  An InputInterface instance
155
     * @param OutputInterface $output
156
     *
157
     * @phpcs:disable SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter
158
     *
159
     * @return int 0 if everything went fine, or an error code
160
     */
161
    protected function execute(InputInterface $input, OutputInterface $output): int
162
    {
163
        $this->checkErrors($input);
164
165
        $extensions = $this->handleBackwardCompatibility($input->getOption('extensions'));
166
        $ignore     = $this->handleBackwardCompatibility($input->getOption('ignore'));
167
168
        $excludePCREParam = (array) $input->getOption('excludePCRE');
169
        $excludePCRE      = $this->convertIgnores($ignore, $excludePCREParam);
170
171
        $logger = new Logger('PHPCodeBrowser');
172
173
        if (!$input->getOption('debugExcludes')) {
174
            $logger->pushHandler(new NullHandler());
175
        }
176
177
        // init new CLIController
178
        $controller = new CLIController(
179
            $input->getOption('log'),
180
            $input->getOption('source'),
181
            $input->getOption('output'),
182
            $excludePCRE,
183
            $input->getOption('exclude'),
184
            ['CRAP' => ['threshold' => $input->getOption('crapThreshold')]],
185
            new IOHelper(),
186
            $logger,
187
            \array_merge($extensions, ['php']),
188
            (bool) $input->getOption('excludeOK')
189
        );
190
191
        $plugins = $this->getAvailablePlugins();
192
        $plugins = $this->disablePlugins((array) $input->getOption('disablePlugin'), $plugins);
193
        $controller->addErrorPlugins($plugins);
194
195
        try {
196
            $controller->run();
197
        } catch (Exception $e) {
198
            \error_log(
199
                <<<HERE
200
                [Error] {$e->getMessage()}
201
202
{$e->getTraceAsString()}
203
HERE
204
            );
205
        }
206
207
        return 0;
208
    }
209
210
    /**
211
     * @param InputInterface $input
212
     *
213
     * @throws \InvalidArgumentException if errors are found
214
     */
215
    protected function checkErrors(InputInterface $input): void
216
    {
217
        if (!$input->getOption('log')) {
218
            if (!$input->getOption('source')) {
219
                throw new \InvalidArgumentException('Missing log or source argument.');
220
            }
221
        } elseif (!\file_exists((string) $input->getOption('log'))) {
222
            throw new \InvalidArgumentException('Log directory does not exist.');
223
        } elseif (!\is_dir((string) $input->getOption('log'))) {
224
            throw new \InvalidArgumentException('Log argument must be a directory, a file was given.');
225
        }
226
227
        if ($input->getOption('source')) {
228
            foreach ($input->getOption('source') as $s) {
229
                if (!\file_exists($s)) {
230
                    throw new \InvalidArgumentException("Source '{$s}' does not exist");
231
                }
232
            }
233
        }
234
235
        if (!$input->getOption('output')) {
236
            throw new \InvalidArgumentException('Missing output argument.');
237
        }
238
239
        if (\file_exists((string) $input->getOption('output')) && !\is_dir((string) $input->getOption('output'))) {
240
            throw new \InvalidArgumentException('Output argument must be a directory, a file was given.');
241
        }
242
    }
243
244
    /**
245
     * Returns a list of available plugins.
246
     *
247
     * Currently hard-coded.
248
     *
249
     * @return array<string> Class names of error plugins
250
     */
251
    protected function getAvailablePlugins(): array
252
    {
253
        return [
254
            ErrorCheckstyle::class,
255
            ErrorPMD::class,
256
            ErrorCPD::class,
257
            ErrorPadawan::class,
258
            ErrorCoverage::class,
259
            ErrorCRAP::class,
260
        ];
261
    }
262
263
    /**
264
     * @param array $disabledPlugins
265
     * @param array $plugins
266
     *
267
     * @return array
268
     */
269
    protected function disablePlugins(array $disabledPlugins, array $plugins): array
270
    {
271
        $disabledPlugins = \array_map(
272
            'strtolower',
273
            $disabledPlugins
274
        );
275
276
        foreach ($plugins as $pluginKey => $plugin) {
277
            $name = \substr($plugin, \strlen('Error'));
278
279
            if (!\in_array(\strtolower($name), $disabledPlugins, true)) {
280
                continue;
281
            }
282
283
            // Remove it from the plugins list
284
            unset($plugins[$pluginKey]);
285
        }
286
287
        return $plugins;
288
    }
289
290
    /**
291
     * Convert the --ignore arguments to patterns
292
     *
293
     * @param array $ignored
294
     * @param array $excludePCRE
295
     *
296
     * @return array
297
     */
298
    protected function convertIgnores(array $ignored, array $excludePCRE): array
299
    {
300
        $dirSep = \preg_quote(DIRECTORY_SEPARATOR, '/');
301
302
        foreach ($ignored as $ignore) {
303
            $ig = \realpath($ignore);
304
305
            if (!$ig) {
306
                \error_log("[Warning] {$ignore} does not exists");
307
            } else {
308
                $ig            = \preg_quote($ig, '/');
309
                $excludePCRE[] = "/^{$ig}({$dirSep}|$)/";
310
            }
311
        }
312
313
        return $excludePCRE;
314
    }
315
316
    /**
317
     * This converts comma-separated options into an array
318
     *
319
     * @param array $option
320
     *
321
     * @return array
322
     */
323
    private function handleBackwardCompatibility(array $option): array
324
    {
325
        if (\count($option) === 1 && \str_contains($option[0], ',')) {
326
            $option = \explode(',', $option[0]);
327
            \error_log('Usage of comma-separated options is deprecated, specify them one-by-one.', E_DEPRECATED);
328
        }
329
330
        return $option;
331
    }
332
}
333