Passed
Push — main ( a387cb...339f6d )
by Cornelia
07:55
created

Command::run()   F

Complexity

Conditions 33
Paths > 20000

Size

Total Lines 179
Code Lines 107

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 107
dl 0
loc 179
rs 0
c 0
b 0
f 0
cc 33
nc 45961
nop 0

How to fix   Long Method    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
 * This file is part of PDepend.
4
 *
5
 * PHP Version 5
6
 *
7
 * Copyright (c) 2008-2017 Manuel Pichler <[email protected]>.
8
 * All rights reserved.
9
 *
10
 * Redistribution and use in source and binary forms, with or without
11
 * modification, are permitted provided that the following conditions
12
 * are met:
13
 *
14
 *   * Redistributions of source code must retain the above copyright
15
 *     notice, this list of conditions and the following disclaimer.
16
 *
17
 *   * Redistributions in binary form must reproduce the above copyright
18
 *     notice, this list of conditions and the following disclaimer in
19
 *     the documentation and/or other materials provided with the
20
 *     distribution.
21
 *
22
 *   * Neither the name of Manuel Pichler nor the names of his
23
 *     contributors may be used to endorse or promote products derived
24
 *     from this software without specific prior written permission.
25
 *
26
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37
 * POSSIBILITY OF SUCH DAMAGE.
38
 *
39
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
40
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
41
 */
42
43
namespace PDepend\TextUI;
44
45
use Exception;
46
use PDepend\Application;
47
use PDepend\DbusUI\ResultPrinter as DbusResultPrinter;
48
use PDepend\Util\ConfigurationInstance;
49
use PDepend\Util\Log;
50
use PDepend\Util\Workarounds;
51
use RuntimeException;
52
53
/**
54
 * Handles the command line stuff and starts the text ui runner.
55
 *
56
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
57
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
58
 */
59
class Command
60
{
61
    /**
62
     * Marks a cli error exit.
63
     */
64
    const CLI_ERROR = 1742;
65
66
    /**
67
     * Marks an input error exit.
68
     */
69
    const INPUT_ERROR = 1743;
70
71
    /**
72
     * The recieved cli options
73
     *
74
     * @var array<string, mixed>
75
     */
76
    private $options = array();
77
78
    /**
79
     * The directories/files to be analyzed
80
     *
81
     * @var array<int, string>
82
     */
83
    private $source = array();
84
85
    /**
86
     * The used text ui runner.
87
     *
88
     * @var Runner
89
     */
90
    private $runner = null;
91
92
    /**
93
     * @var Application
94
     */
95
    private $application;
96
97
    /**
98
     * Performs the main cli process and returns the exit code.
99
     *
100
     * @return int
101
     */
102
    public function run()
103
    {
104
        $this->application = new Application();
105
106
        try {
107
            if ($this->parseArguments() === false) {
108
                $this->printHelp();
109
                return self::CLI_ERROR;
110
            }
111
        } catch (Exception $e) {
112
            echo $e->getMessage(), PHP_EOL, PHP_EOL;
113
114
            $this->printHelp();
115
            return self::CLI_ERROR;
116
        }
117
118
        if (isset($this->options['--help'])) {
119
            $this->printHelp();
120
            return Runner::SUCCESS_EXIT;
121
        }
122
        if (isset($this->options['--usage'])) {
123
            $this->printUsage();
124
            return Runner::SUCCESS_EXIT;
125
        }
126
        if (isset($this->options['--version'])) {
127
            $this->printVersion();
128
            return Runner::SUCCESS_EXIT;
129
        }
130
131
        $configurationFile = false;
132
133
        if (isset($this->options['--configuration'])) {
134
            $configurationFile = $this->options['--configuration'];
135
136
            if (false === file_exists($configurationFile)) {
137
                $configurationFile = getcwd() . '/' . $configurationFile;
138
            }
139
            if (false === file_exists($configurationFile)) {
140
                $configurationFile = $this->options['--configuration'];
141
            }
142
143
            unset($this->options['--configuration']);
144
        } elseif (file_exists(getcwd() . '/pdepend.xml')) {
145
            $configurationFile = getcwd() . '/pdepend.xml';
146
        } elseif (file_exists(getcwd() . '/pdepend.xml.dist')) {
147
            $configurationFile = getcwd() . '/pdepend.xml.dist';
148
        }
149
150
        if ($configurationFile) {
151
            try {
152
                $this->application->setConfigurationFile($configurationFile);
153
            } catch (Exception $e) {
154
                echo $e->getMessage(), PHP_EOL, PHP_EOL;
155
156
                $this->printHelp();
157
                return self::CLI_ERROR;
158
            }
159
        }
160
161
        // Create a new text ui runner
162
        $this->runner = $this->application->getRunner();
163
164
        $this->assignArguments();
165
166
        // Get a copy of all options
167
        $options = $this->options;
168
169
        // Get an array with all available log options
170
        $logOptions = $this->application->getAvailableLoggerOptions();
171
172
        // Get an array with all available analyzer options
173
        $analyzerOptions = $this->application->getAvailableAnalyzerOptions();
174
175
        foreach ($options as $option => $value) {
176
            if (isset($logOptions[$option])) {
177
                // Reduce received option list
178
                unset($options[$option]);
179
                // Register logger
180
                $this->runner->addReportGenerator(substr($option, 2), $value);
181
            } elseif (isset($analyzerOptions[$option])) {
182
                // Reduce received option list
183
                unset($options[$option]);
184
185
                if (isset($analyzerOptions[$option]['value']) && is_bool($value)) {
186
                    echo 'Option ', $option, ' requires a value.', PHP_EOL;
187
                    return self::INPUT_ERROR;
188
                } elseif ($analyzerOptions[$option]['value'] === 'file'
189
                    && file_exists($value) === false
190
                ) {
191
                    echo 'Specified file ', $option, '=', $value,
192
                         ' not exists.', PHP_EOL;
193
194
                    return self::INPUT_ERROR;
195
                } elseif ($analyzerOptions[$option]['value'] === '*[,...]') {
196
                    $value = array_map('trim', explode(',', $value));
197
                }
198
                $this->runner->addOption(substr($option, 2), $value);
199
            }
200
        }
201
202
        if (isset($options['--without-annotations'])) {
203
            // Disable annotation parsing
204
            $this->runner->setWithoutAnnotations();
205
            // Remove option
206
            unset($options['--without-annotations']);
207
        }
208
209
        if (isset($options['--optimization'])) {
210
            // This option is deprecated.
211
            echo 'Option --optimization is ambiguous.', PHP_EOL;
212
            // Remove option
213
            unset($options['--optimization']);
214
        }
215
216
        if (isset($options['--quiet'])) {
217
            $runSilent = true;
218
            unset($options['--quiet']);
219
        } else {
220
            $runSilent = false;
221
            $this->runner->addProcessListener(new ResultPrinter());
222
        }
223
224
        if (isset($options['--notify-me'])) {
225
            $this->runner->addProcessListener(
226
                new DbusResultPrinter()
227
            );
228
            unset($options['--notify-me']);
229
        }
230
231
        if (count($options) > 0) {
232
            $this->printHelp();
233
            echo "Unknown option '", key($options), "' given.", PHP_EOL;
234
            return self::CLI_ERROR;
235
        }
236
237
        try {
238
            // Output current pdepend version and author
239
            if ($runSilent === false) {
240
                $this->printVersion();
241
                $this->printWorkarounds();
242
            }
243
244
            $startTime = time();
245
246
            $result = $this->runner->run();
247
248
            if ($this->runner->hasParseErrors() === true) {
249
                $errors = $this->runner->getParseErrors();
250
251
                printf(
252
                    '%sThe following error%s occurred:%s',
253
                    PHP_EOL,
254
                    count($errors) > 1 ? 's' : '',
255
                    PHP_EOL
256
                );
257
258
                foreach ($errors as $error) {
259
                    echo $error, PHP_EOL;
260
                }
261
                echo PHP_EOL;
262
            }
263
            if ($runSilent === false) {
264
                $this->printStatistics($startTime);
265
            }
266
267
            return $result;
268
        } catch (RuntimeException $e) {
269
            echo PHP_EOL, PHP_EOL,
270
                 'Critical error:', PHP_EOL,
271
                 '===============', PHP_EOL,
272
                  $e->getMessage(), PHP_EOL;
273
274
            Log::debug($this->getErrorTrace($e));
275
276
            for ($previous = $e->getPrevious(); $previous; $previous = $previous->getPrevious()) {
277
                Log::debug(PHP_EOL . 'Caused by:' . PHP_EOL . $this->getErrorTrace($previous));
278
            }
279
280
            return $e->getCode();
281
        }
282
    }
283
284
    /**
285
     * Parses the cli arguments.
286
     *
287
     * @return bool
288
     */
289
    protected function parseArguments()
290
    {
291
        if (!isset($_SERVER['argv'])) {
292
            if (false === (boolean) ini_get('register_argc_argv')) {
293
                // @codeCoverageIgnoreStart
294
                echo 'Please enable register_argc_argv in your php.ini.';
295
            } else {
296
                // @codeCoverageIgnoreEnd
297
                echo 'Unknown error, no $argv array available.';
298
            }
299
            echo PHP_EOL, PHP_EOL;
300
            return false;
301
        }
302
303
        $argv = $_SERVER['argv'];
304
305
        // Remove the pdepend command line file
306
        array_shift($argv);
307
308
        if (count($argv) === 0) {
309
            return false;
310
        }
311
312
        // Last argument must be a list of source directories
313
        if (strpos(end($argv), '--') !== 0) {
314
            $this->source = explode(',', array_pop($argv));
315
        }
316
317
        for ($i = 0, $c = count($argv); $i < $c; ++$i) {
318
            // Is it an ini_set option?
319
            $arg = (string)$argv[$i];
320
            if ($arg === '-d' && isset($argv[$i + 1])) {
321
                $arg = (string)$argv[++$i];
322
                if (strpos($arg, '=') === false) {
323
                    ini_set($arg, 'on');
324
                } else {
325
                    list($key, $value) = explode('=', $arg);
326
327
                    ini_set($key, $value);
328
                }
329
            } elseif (strpos($arg, '=') === false) {
330
                $this->options[$arg] = true;
331
            } else {
332
                list($key, $value) = explode('=', $arg);
333
334
                $this->options[$key] = $value;
335
            }
336
        }
337
338
        return true;
339
    }
340
341
    /**
342
     * Assign CLI arguments to current runner instance
343
     *
344
     * @return void
345
     */
346
    protected function assignArguments()
347
    {
348
        if ($this->source) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->source of type array<integer,string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
349
            $this->runner->setSourceArguments($this->source);
350
        }
351
352
        // Check for suffix option
353
        if (isset($this->options['--suffix'])) {
354
            // Get file extensions
355
            $extensions = explode(',', $this->options['--suffix']);
356
            // Set allowed file extensions
357
            $this->runner->setFileExtensions($extensions);
358
359
            unset($this->options['--suffix']);
360
        }
361
362
        // Check for ignore option
363
        if (isset($this->options['--ignore'])) {
364
            // Get exclude directories
365
            $directories = explode(',', $this->options['--ignore']);
366
            // Set exclude directories
367
            $this->runner->setExcludeDirectories($directories);
368
369
            unset($this->options['--ignore']);
370
        }
371
372
        // Check for exclude namespace option
373
        if (isset($this->options['--exclude'])) {
374
            // Get exclude directories
375
            $namespaces = explode(',', $this->options['--exclude']);
376
            // Set exclude namespace
377
            $this->runner->setExcludeNamespaces($namespaces);
378
379
            unset($this->options['--exclude']);
380
        }
381
382
        // Check for the bad documentation option
383
        if (isset($this->options['--bad-documentation'])) {
384
            echo "Option --bad-documentation is ambiguous.", PHP_EOL;
385
386
            unset($this->options['--bad-documentation']);
387
        }
388
389
        $configuration = $this->application->getConfiguration();
390
391
        // Store in config registry
392
        ConfigurationInstance::set($configuration);
393
394
        if (isset($this->options['--debug'])) {
395
            unset($this->options['--debug']);
396
397
            Log::setSeverity(Log::DEBUG);
398
        }
399
    }
400
401
    /**
402
     * Outputs the current PDepend version.
403
     *
404
     * @return void
405
     */
406
    protected function printVersion()
407
    {
408
        $build = __DIR__ . '/../../../../../build.properties';
409
410
        $version = '@package_version@';
411
        if (file_exists($build)) {
412
            $data = @parse_ini_file($build);
413
            if (is_array($data)) {
414
                $version = $data['project.version'];
415
            }
416
        }
417
418
        echo 'PDepend ', $version, PHP_EOL, PHP_EOL;
419
    }
420
421
    /**
422
     * If the current PHP installation requires some workarounds or limitations,
423
     * this method will output a message on STDOUT.
424
     *
425
     * @return void
426
     */
427
    protected function printWorkarounds()
428
    {
429
        $workarounds = new Workarounds();
430
431
        if ($workarounds->isNotRequired()) {
432
            return;
433
        }
434
435
        echo 'Your PHP version requires some workaround:', PHP_EOL;
436
        foreach ($workarounds->getRequiredWorkarounds() as $workaround) {
437
            echo '- ', $workaround, PHP_EOL;
438
        }
439
        echo PHP_EOL;
440
    }
441
442
    /**
443
     * Outputs the base usage of PDepend.
444
     *
445
     * @return void
446
     */
447
    protected function printUsage()
448
    {
449
        $this->printVersion();
450
        echo 'Usage: pdepend [options] [logger] <dir[,dir[,...]]>', PHP_EOL, PHP_EOL;
451
    }
452
453
    /**
454
     * Outputs the main help of PDepend.
455
     *
456
     * @return void
457
     */
458
    protected function printHelp()
459
    {
460
        $this->printUsage();
461
462
        $length = $this->printLogOptions();
463
        $length = $this->printAnalyzerOptions($length);
464
465
        $this->printOption(
466
            '--configuration=<file>',
467
            'Optional PDepend configuration file.',
468
            $length
469
        );
470
        echo PHP_EOL;
471
472
        $this->printOption(
473
            '--suffix=<ext[,...]>',
474
            'List of valid PHP file extensions.',
475
            $length
476
        );
477
        $this->printOption(
478
            '--ignore=<dir[,...]>',
479
            'List of exclude directories.',
480
            $length
481
        );
482
        $this->printOption(
483
            '--exclude=<pkg[,...]>',
484
            'List of exclude namespaces.',
485
            $length
486
        );
487
        echo PHP_EOL;
488
489
        $this->printOption(
490
            '--without-annotations',
491
            'Do not parse doc comment annotations.',
492
            $length
493
        );
494
        echo PHP_EOL;
495
496
        $this->printOption('--quiet', 'Prints errors only.', $length);
497
        $this->printOption('--debug', 'Prints debugging information.', $length);
498
        $this->printOption('--help', 'Print this help text.', $length);
499
        $this->printOption('--version', 'Print the current version.', $length);
500
501
        $this->printDbusOption($length);
502
503
        $this->printOption('-d key[=value]', 'Sets a php.ini value.', $length);
504
        echo PHP_EOL;
505
    }
506
507
    /**
508
     * Prints all available log options and returns the length of the longest
509
     * option.
510
     *
511
     * @return int
512
     */
513
    protected function printLogOptions()
514
    {
515
        $maxLength = 0;
516
        $options   = array();
517
        $logOptions = $this->application->getAvailableLoggerOptions();
518
        foreach ($logOptions as $option => $info) {
519
            // Build log option identifier
520
            $identifier = sprintf('%s=<%s>', $option, $info['value']);
521
            // Store in options array
522
            $options[$identifier] = $info['message'];
523
524
            $length = strlen($identifier);
525
            if ($length > $maxLength) {
526
                $maxLength = $length;
527
            }
528
        }
529
530
        ksort($options);
531
532
        $last = null;
533
        foreach ($options as $option => $message) {
534
            $pos = strrpos($option, '-');
535
            $current = substr($option, 0, $pos === false ? null : $pos);
536
            if ($last !== null && $last !== $current) {
537
                echo PHP_EOL;
538
            }
539
            $last = $current;
540
541
            $this->printOption($option, $message, $maxLength);
542
        }
543
        echo PHP_EOL;
544
545
        return $maxLength;
546
    }
547
548
    /**
549
     * Prints the analyzer options.
550
     *
551
     * @param int $length Length of the longest option.
552
     *
553
     * @return int
554
     */
555
    protected function printAnalyzerOptions($length)
556
    {
557
        $options = $this->application->getAvailableAnalyzerOptions();
558
559
        if (count($options) === 0) {
560
            return $length;
561
        }
562
563
        ksort($options);
564
565
        foreach ($options as $option => $info) {
566
            if (isset($info['value'])) {
567
                $option .= '=<' . $info['value'] . '>';
568
            } else {
569
                $option .= '=<value>';
570
            }
571
572
            $this->printOption($option, $info['message'], $length);
573
        }
574
        echo PHP_EOL;
575
576
        return $length;
577
    }
578
579
    /**
580
     * Prints a single option.
581
     *
582
     * @param string $option  The option identifier.
583
     * @param string $message The option help message.
584
     * @param int    $length  The length of the longest option.
585
     *
586
     * @return void
587
     */
588
    private function printOption($option, $message, $length)
589
    {
590
        // Ignore the phpunit xml option
591
        if (0 === strpos($option, '--phpunit-xml=')) {
592
            return;
593
        }
594
595
        // Calculate the max message length
596
        $mlength = 77 - $length;
597
598
        $option = str_pad($option, $length, ' ', STR_PAD_RIGHT);
599
        echo '  ', $option, ' ';
600
601
        $lines = explode(PHP_EOL, wordwrap($message, $mlength, PHP_EOL));
602
        echo array_shift($lines);
603
604
        while (($line = array_shift($lines)) !== null) {
605
            echo PHP_EOL, str_repeat(' ', $length + 3), $line;
606
        }
607
        echo PHP_EOL;
608
    }
609
610
    /**
611
     * Optionally outputs the dbus option when the required extension
612
     * is loaded.
613
     *
614
     * @param int $length Padding length for the option.
615
     *
616
     * @return void
617
     */
618
    private function printDbusOption($length)
619
    {
620
        if (extension_loaded("dbus") === false) {
621
            return;
622
        }
623
624
        $option  = '--notify-me';
625
        $message = 'Show a notification after analysis.';
626
627
        $this->printOption($option, $message, $length);
628
    }
629
630
    /**
631
     * Main method that starts the command line runner.
632
     *
633
     * @return int The exit code.
634
     */
635
    public static function main()
636
    {
637
        $command = new self();
638
639
        return $command->run();
640
    }
641
642
    /**
643
     * @param int $startTime
644
     *
645
     * @return void
646
     */
647
    private function printStatistics($startTime)
648
    {
649
        $duration = time() - $startTime;
650
        $hours = intval($duration / 3600);
651
        $minutes = intval(($duration - $hours * 3600) / 60);
652
        $seconds = $duration % 60;
653
        echo PHP_EOL, 'Time: ', sprintf('%d:%02d:%02d', $hours, $minutes, $seconds);
654
        if (function_exists('memory_get_peak_usage')) {
655
            $memory = (memory_get_peak_usage(true) / (1024 * 1024));
656
            printf('; Memory: %4.2fMb', $memory);
657
        }
658
        echo PHP_EOL;
659
    }
660
661
    /**
662
     * @param Exception|\Throwable $exception
663
     *
664
     * @return string
665
     */
666
    private function getErrorTrace($exception)
667
    {
668
        return get_class($exception) . '(' . $exception->getMessage() . ')' . PHP_EOL .
669
            '## ' . $exception->getFile() .'(' . $exception->getLine() . ')' . PHP_EOL .
670
            $exception->getTraceAsString();
671
    }
672
}
673