GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Application::getHelp()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Console;
13
14
use Symfony\Component\Console\Descriptor\TextDescriptor;
15
use Symfony\Component\Console\Descriptor\XmlDescriptor;
16
use Symfony\Component\Console\Helper\DebugFormatterHelper;
17
use Symfony\Component\Console\Helper\ProcessHelper;
18
use Symfony\Component\Console\Helper\QuestionHelper;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\ArgvInput;
21
use Symfony\Component\Console\Input\ArrayInput;
22
use Symfony\Component\Console\Input\InputDefinition;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Input\InputArgument;
25
use Symfony\Component\Console\Input\InputAwareInterface;
26
use Symfony\Component\Console\Output\BufferedOutput;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Output\ConsoleOutput;
29
use Symfony\Component\Console\Output\ConsoleOutputInterface;
30
use Symfony\Component\Console\Command\Command;
31
use Symfony\Component\Console\Command\HelpCommand;
32
use Symfony\Component\Console\Command\ListCommand;
33
use Symfony\Component\Console\Helper\HelperSet;
34
use Symfony\Component\Console\Helper\FormatterHelper;
35
use Symfony\Component\Console\Helper\DialogHelper;
36
use Symfony\Component\Console\Helper\ProgressHelper;
37
use Symfony\Component\Console\Helper\TableHelper;
38
use Symfony\Component\Console\Event\ConsoleCommandEvent;
39
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
40
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
41
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
42
43
/**
44
 * An Application is the container for a collection of commands.
45
 *
46
 * It is the main entry point of a Console application.
47
 *
48
 * This class is optimized for a standard CLI environment.
49
 *
50
 * Usage:
51
 *
52
 *     $app = new Application('myapp', '1.0 (stable)');
53
 *     $app->add(new SimpleCommand());
54
 *     $app->run();
55
 *
56
 * @author Fabien Potencier <[email protected]>
57
 *
58
 * @api
59
 */
60
class Application
61
{
62
    private $commands = array();
63
    private $wantHelps = false;
64
    private $runningCommand;
65
    private $name;
66
    private $version;
67
    private $catchExceptions = true;
68
    private $autoExit = true;
69
    private $definition;
70
    private $helperSet;
71
    private $dispatcher;
72
    private $terminalDimensions;
73
    private $defaultCommand;
74
75
    /**
76
     * Constructor.
77
     *
78
     * @param string $name    The name of the application
79
     * @param string $version The version of the application
80
     *
81
     * @api
82
     */
83
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
84
    {
85
        $this->name = $name;
86
        $this->version = $version;
87
        $this->defaultCommand = 'list';
88
        $this->helperSet = $this->getDefaultHelperSet();
89
        $this->definition = $this->getDefaultInputDefinition();
90
91
        foreach ($this->getDefaultCommands() as $command) {
92
            $this->add($command);
93
        }
94
    }
95
96
    public function setDispatcher(EventDispatcherInterface $dispatcher)
97
    {
98
        $this->dispatcher = $dispatcher;
99
    }
100
101
    /**
102
     * Runs the current application.
103
     *
104
     * @param InputInterface  $input  An Input instance
105
     * @param OutputInterface $output An Output instance
106
     *
107
     * @return int 0 if everything went fine, or an error code
108
     *
109
     * @throws \Exception When doRun returns Exception
110
     *
111
     * @api
112
     */
113
    public function run(InputInterface $input = null, OutputInterface $output = null)
114
    {
115
        if (null === $input) {
116
            $input = new ArgvInput();
117
        }
118
119
        if (null === $output) {
120
            $output = new ConsoleOutput();
121
        }
122
123
        $this->configureIO($input, $output);
124
125
        try {
126
            $exitCode = $this->doRun($input, $output);
127
        } catch (\Exception $e) {
128
            if (!$this->catchExceptions) {
129
                throw $e;
130
            }
131
132
            if ($output instanceof ConsoleOutputInterface) {
133
                $this->renderException($e, $output->getErrorOutput());
134
            } else {
135
                $this->renderException($e, $output);
136
            }
137
138
            $exitCode = $e->getCode();
139
            if (is_numeric($exitCode)) {
140
                $exitCode = (int) $exitCode;
141
                if (0 === $exitCode) {
142
                    $exitCode = 1;
143
                }
144
            } else {
145
                $exitCode = 1;
146
            }
147
        }
148
149
        if ($this->autoExit) {
150
            if ($exitCode > 255) {
151
                $exitCode = 255;
152
            }
153
154
            exit($exitCode);
0 ignored issues
show
Coding Style Compatibility introduced by
The method run() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
155
        }
156
157
        return $exitCode;
158
    }
159
160
    /**
161
     * Runs the current application.
162
     *
163
     * @param InputInterface  $input  An Input instance
164
     * @param OutputInterface $output An Output instance
165
     *
166
     * @return int 0 if everything went fine, or an error code
167
     */
168
    public function doRun(InputInterface $input, OutputInterface $output)
169
    {
170
        if (true === $input->hasParameterOption(array('--version', '-V'))) {
171
            $output->writeln($this->getLongVersion());
172
173
            return 0;
174
        }
175
176
        $name = $this->getCommandName($input);
177
        if (true === $input->hasParameterOption(array('--help', '-h'))) {
178
            if (!$name) {
179
                $name = 'help';
180
                $input = new ArrayInput(array('command' => 'help'));
181
            } else {
182
                $this->wantHelps = true;
183
            }
184
        }
185
186
        if (!$name) {
187
            $name = $this->defaultCommand;
188
            $input = new ArrayInput(array('command' => $this->defaultCommand));
189
        }
190
191
        // the command name MUST be the first element of the input
192
        $command = $this->find($name);
193
194
        $this->runningCommand = $command;
195
        $exitCode = $this->doRunCommand($command, $input, $output);
196
        $this->runningCommand = null;
197
198
        return $exitCode;
199
    }
200
201
    /**
202
     * Set a helper set to be used with the command.
203
     *
204
     * @param HelperSet $helperSet The helper set
205
     *
206
     * @api
207
     */
208
    public function setHelperSet(HelperSet $helperSet)
209
    {
210
        $this->helperSet = $helperSet;
211
    }
212
213
    /**
214
     * Get the helper set associated with the command.
215
     *
216
     * @return HelperSet The HelperSet instance associated with this command
217
     *
218
     * @api
219
     */
220
    public function getHelperSet()
221
    {
222
        return $this->helperSet;
223
    }
224
225
    /**
226
     * Set an input definition set to be used with this application.
227
     *
228
     * @param InputDefinition $definition The input definition
229
     *
230
     * @api
231
     */
232
    public function setDefinition(InputDefinition $definition)
233
    {
234
        $this->definition = $definition;
235
    }
236
237
    /**
238
     * Gets the InputDefinition related to this Application.
239
     *
240
     * @return InputDefinition The InputDefinition instance
241
     */
242
    public function getDefinition()
243
    {
244
        return $this->definition;
245
    }
246
247
    /**
248
     * Gets the help message.
249
     *
250
     * @return string A help message.
251
     */
252
    public function getHelp()
253
    {
254
        return $this->getLongVersion();
255
    }
256
257
    /**
258
     * Sets whether to catch exceptions or not during commands execution.
259
     *
260
     * @param bool $boolean Whether to catch exceptions or not during commands execution
261
     *
262
     * @api
263
     */
264
    public function setCatchExceptions($boolean)
265
    {
266
        $this->catchExceptions = (bool) $boolean;
267
    }
268
269
    /**
270
     * Sets whether to automatically exit after a command execution or not.
271
     *
272
     * @param bool $boolean Whether to automatically exit after a command execution or not
273
     *
274
     * @api
275
     */
276
    public function setAutoExit($boolean)
277
    {
278
        $this->autoExit = (bool) $boolean;
279
    }
280
281
    /**
282
     * Gets the name of the application.
283
     *
284
     * @return string The application name
285
     *
286
     * @api
287
     */
288
    public function getName()
289
    {
290
        return $this->name;
291
    }
292
293
    /**
294
     * Sets the application name.
295
     *
296
     * @param string $name The application name
297
     *
298
     * @api
299
     */
300
    public function setName($name)
301
    {
302
        $this->name = $name;
303
    }
304
305
    /**
306
     * Gets the application version.
307
     *
308
     * @return string The application version
309
     *
310
     * @api
311
     */
312
    public function getVersion()
313
    {
314
        return $this->version;
315
    }
316
317
    /**
318
     * Sets the application version.
319
     *
320
     * @param string $version The application version
321
     *
322
     * @api
323
     */
324
    public function setVersion($version)
325
    {
326
        $this->version = $version;
327
    }
328
329
    /**
330
     * Returns the long version of the application.
331
     *
332
     * @return string The long application version
333
     *
334
     * @api
335
     */
336
    public function getLongVersion()
337
    {
338
        if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
339
            return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
340
        }
341
342
        return '<info>Console Tool</info>';
343
    }
344
345
    /**
346
     * Registers a new command.
347
     *
348
     * @param string $name The command name
349
     *
350
     * @return Command The newly created command
351
     *
352
     * @api
353
     */
354
    public function register($name)
355
    {
356
        return $this->add(new Command($name));
357
    }
358
359
    /**
360
     * Adds an array of command objects.
361
     *
362
     * @param Command[] $commands An array of commands
363
     *
364
     * @api
365
     */
366
    public function addCommands(array $commands)
367
    {
368
        foreach ($commands as $command) {
369
            $this->add($command);
370
        }
371
    }
372
373
    /**
374
     * Adds a command object.
375
     *
376
     * If a command with the same name already exists, it will be overridden.
377
     *
378
     * @param Command $command A Command object
379
     *
380
     * @return Command The registered command
381
     *
382
     * @api
383
     */
384
    public function add(Command $command)
385
    {
386
        $command->setApplication($this);
387
388
        if (!$command->isEnabled()) {
389
            $command->setApplication(null);
390
391
            return;
392
        }
393
394
        if (null === $command->getDefinition()) {
395
            throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
396
        }
397
398
        $this->commands[$command->getName()] = $command;
399
400
        foreach ($command->getAliases() as $alias) {
401
            $this->commands[$alias] = $command;
402
        }
403
404
        return $command;
405
    }
406
407
    /**
408
     * Returns a registered command by name or alias.
409
     *
410
     * @param string $name The command name or alias
411
     *
412
     * @return Command A Command object
413
     *
414
     * @throws \InvalidArgumentException When command name given does not exist
415
     *
416
     * @api
417
     */
418
    public function get($name)
419
    {
420
        if (!isset($this->commands[$name])) {
421
            throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
422
        }
423
424
        $command = $this->commands[$name];
425
426
        if ($this->wantHelps) {
427
            $this->wantHelps = false;
428
429
            $helpCommand = $this->get('help');
430
            $helpCommand->setCommand($command);
431
432
            return $helpCommand;
433
        }
434
435
        return $command;
436
    }
437
438
    /**
439
     * Returns true if the command exists, false otherwise.
440
     *
441
     * @param string $name The command name or alias
442
     *
443
     * @return bool true if the command exists, false otherwise
444
     *
445
     * @api
446
     */
447
    public function has($name)
448
    {
449
        return isset($this->commands[$name]);
450
    }
451
452
    /**
453
     * Returns an array of all unique namespaces used by currently registered commands.
454
     *
455
     * It does not returns the global namespace which always exists.
456
     *
457
     * @return array An array of namespaces
458
     */
459
    public function getNamespaces()
460
    {
461
        $namespaces = array();
462
        foreach ($this->commands as $command) {
463
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
464
465
            foreach ($command->getAliases() as $alias) {
466
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
467
            }
468
        }
469
470
        return array_values(array_unique(array_filter($namespaces)));
471
    }
472
473
    /**
474
     * Finds a registered namespace by a name or an abbreviation.
475
     *
476
     * @param string $namespace A namespace or abbreviation to search for
477
     *
478
     * @return string A registered namespace
479
     *
480
     * @throws \InvalidArgumentException When namespace is incorrect or ambiguous
481
     */
482
    public function findNamespace($namespace)
483
    {
484
        $allNamespaces = $this->getNamespaces();
485
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
486
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
487
488
        if (empty($namespaces)) {
489
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
490
491 View Code Duplication
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces, array())) {
0 ignored issues
show
Unused Code introduced by
The call to Application::findAlternatives() has too many arguments starting with array().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
492
                if (1 == count($alternatives)) {
493
                    $message .= "\n\nDid you mean this?\n    ";
494
                } else {
495
                    $message .= "\n\nDid you mean one of these?\n    ";
496
                }
497
498
                $message .= implode("\n    ", $alternatives);
499
            }
500
501
            throw new \InvalidArgumentException($message);
502
        }
503
504
        $exact = in_array($namespace, $namespaces, true);
505 View Code Duplication
        if (count($namespaces) > 1 && !$exact) {
506
            throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
507
        }
508
509
        return $exact ? $namespace : reset($namespaces);
510
    }
511
512
    /**
513
     * Finds a command by name or alias.
514
     *
515
     * Contrary to get, this command tries to find the best
516
     * match if you give it an abbreviation of a name or alias.
517
     *
518
     * @param string $name A command name or a command alias
519
     *
520
     * @return Command A Command instance
521
     *
522
     * @throws \InvalidArgumentException When command name is incorrect or ambiguous
523
     *
524
     * @api
525
     */
526
    public function find($name)
527
    {
528
        $allCommands = array_keys($this->commands);
529
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
530
        $commands = preg_grep('{^'.$expr.'}', $allCommands);
531
532
        if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
533
            if (false !== $pos = strrpos($name, ':')) {
534
                // check if a namespace exists and contains commands
535
                $this->findNamespace(substr($name, 0, $pos));
536
            }
537
538
            $message = sprintf('Command "%s" is not defined.', $name);
539
540 View Code Duplication
            if ($alternatives = $this->findAlternatives($name, $allCommands, array())) {
0 ignored issues
show
Unused Code introduced by
The call to Application::findAlternatives() has too many arguments starting with array().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
541
                if (1 == count($alternatives)) {
542
                    $message .= "\n\nDid you mean this?\n    ";
543
                } else {
544
                    $message .= "\n\nDid you mean one of these?\n    ";
545
                }
546
                $message .= implode("\n    ", $alternatives);
547
            }
548
549
            throw new \InvalidArgumentException($message);
550
        }
551
552
        // filter out aliases for commands which are already on the list
553
        if (count($commands) > 1) {
554
            $commandList = $this->commands;
555
            $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
556
                $commandName = $commandList[$nameOrAlias]->getName();
557
558
                return $commandName === $nameOrAlias || !in_array($commandName, $commands);
559
            });
560
        }
561
562
        $exact = in_array($name, $commands, true);
563 View Code Duplication
        if (count($commands) > 1 && !$exact) {
564
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
565
566
            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
567
        }
568
569
        return $this->get($exact ? $name : reset($commands));
570
    }
571
572
    /**
573
     * Gets the commands (registered in the given namespace if provided).
574
     *
575
     * The array keys are the full names and the values the command instances.
576
     *
577
     * @param string $namespace A namespace name
578
     *
579
     * @return Command[] An array of Command instances
580
     *
581
     * @api
582
     */
583
    public function all($namespace = null)
584
    {
585
        if (null === $namespace) {
586
            return $this->commands;
587
        }
588
589
        $commands = array();
590
        foreach ($this->commands as $name => $command) {
591
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
592
                $commands[$name] = $command;
593
            }
594
        }
595
596
        return $commands;
597
    }
598
599
    /**
600
     * Returns an array of possible abbreviations given a set of names.
601
     *
602
     * @param array $names An array of names
603
     *
604
     * @return array An array of abbreviations
605
     */
606
    public static function getAbbreviations($names)
607
    {
608
        $abbrevs = array();
609
        foreach ($names as $name) {
610
            for ($len = strlen($name); $len > 0; --$len) {
611
                $abbrev = substr($name, 0, $len);
612
                $abbrevs[$abbrev][] = $name;
613
            }
614
        }
615
616
        return $abbrevs;
617
    }
618
619
    /**
620
     * Returns a text representation of the Application.
621
     *
622
     * @param string $namespace An optional namespace name
623
     * @param bool   $raw       Whether to return raw command list
624
     *
625
     * @return string A string representing the Application
626
     *
627
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
628
     */
629 View Code Duplication
    public function asText($namespace = null, $raw = false)
630
    {
631
        $descriptor = new TextDescriptor();
632
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw);
633
        $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true));
634
635
        return $output->fetch();
636
    }
637
638
    /**
639
     * Returns an XML representation of the Application.
640
     *
641
     * @param string $namespace An optional namespace name
642
     * @param bool   $asDom     Whether to return a DOM or an XML string
643
     *
644
     * @return string|\DOMDocument An XML string representing the Application
645
     *
646
     * @deprecated Deprecated since version 2.3, to be removed in 3.0.
647
     */
648 View Code Duplication
    public function asXml($namespace = null, $asDom = false)
649
    {
650
        $descriptor = new XmlDescriptor();
651
652
        if ($asDom) {
653
            return $descriptor->getApplicationDocument($this, $namespace);
654
        }
655
656
        $output = new BufferedOutput();
657
        $descriptor->describe($output, $this, array('namespace' => $namespace));
658
659
        return $output->fetch();
660
    }
661
662
    /**
663
     * Renders a caught exception.
664
     *
665
     * @param \Exception      $e      An exception instance
666
     * @param OutputInterface $output An OutputInterface instance
667
     */
668
    public function renderException($e, $output)
669
    {
670
        do {
671
            $title = sprintf('  [%s]  ', get_class($e));
672
673
            $len = $this->stringWidth($title);
674
675
            $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
676
            // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
677
            if (defined('HHVM_VERSION') && $width > 1 << 31) {
678
                $width = 1 << 31;
679
            }
680
            $formatter = $output->getFormatter();
681
            $lines = array();
682
            foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
683
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
684
                    // pre-format lines to get the right string length
685
                    $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4;
686
                    $lines[] = array($line, $lineLength);
687
688
                    $len = max($lineLength, $len);
689
                }
690
            }
691
692
            $messages = array('', '');
693
            $messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len)));
694
            $messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
695
            foreach ($lines as $line) {
696
                $messages[] = $formatter->format(sprintf('<error>  %s  %s</error>', $line[0], str_repeat(' ', $len - $line[1])));
697
            }
698
            $messages[] = $emptyLine;
699
            $messages[] = '';
700
            $messages[] = '';
701
702
            $output->writeln($messages, OutputInterface::OUTPUT_RAW);
703
704
            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
705
                $output->writeln('<comment>Exception trace:</comment>');
706
707
                // exception related properties
708
                $trace = $e->getTrace();
709
                array_unshift($trace, array(
710
                    'function' => '',
711
                    'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
712
                    'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
713
                    'args' => array(),
714
                ));
715
716
                for ($i = 0, $count = count($trace); $i < $count; $i++) {
717
                    $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
718
                    $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
719
                    $function = $trace[$i]['function'];
720
                    $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
721
                    $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
722
723
                    $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
724
                }
725
726
                $output->writeln('');
727
                $output->writeln('');
728
            }
729
        } while ($e = $e->getPrevious());
730
731
        if (null !== $this->runningCommand) {
732
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
0 ignored issues
show
Bug introduced by
The method getSynopsis cannot be called on $this->runningCommand (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
733
            $output->writeln('');
734
            $output->writeln('');
735
        }
736
    }
737
738
    /**
739
     * Tries to figure out the terminal width in which this application runs.
740
     *
741
     * @return int|null
742
     */
743
    protected function getTerminalWidth()
744
    {
745
        $dimensions = $this->getTerminalDimensions();
746
747
        return $dimensions[0];
748
    }
749
750
    /**
751
     * Tries to figure out the terminal height in which this application runs.
752
     *
753
     * @return int|null
754
     */
755
    protected function getTerminalHeight()
756
    {
757
        $dimensions = $this->getTerminalDimensions();
758
759
        return $dimensions[1];
760
    }
761
762
    /**
763
     * Tries to figure out the terminal dimensions based on the current environment.
764
     *
765
     * @return array Array containing width and height
766
     */
767
    public function getTerminalDimensions()
768
    {
769
        if ($this->terminalDimensions) {
770
            return $this->terminalDimensions;
771
        }
772
773
        if ('\\' === DIRECTORY_SEPARATOR) {
774
            // extract [w, H] from "wxh (WxH)"
775
            if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
776
                return array((int) $matches[1], (int) $matches[2]);
777
            }
778
            // extract [w, h] from "wxh"
779
            if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
780
                return array((int) $matches[1], (int) $matches[2]);
781
            }
782
        }
783
784
        if ($sttyString = $this->getSttyColumns()) {
785
            // extract [w, h] from "rows h; columns w;"
786 View Code Duplication
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
787
                return array((int) $matches[2], (int) $matches[1]);
788
            }
789
            // extract [w, h] from "; h rows; w columns"
790 View Code Duplication
            if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
791
                return array((int) $matches[2], (int) $matches[1]);
792
            }
793
        }
794
795
        return array(null, null);
796
    }
797
798
    /**
799
     * Sets terminal dimensions.
800
     *
801
     * Can be useful to force terminal dimensions for functional tests.
802
     *
803
     * @param int $width  The width
804
     * @param int $height The height
805
     *
806
     * @return Application The current application
807
     */
808
    public function setTerminalDimensions($width, $height)
809
    {
810
        $this->terminalDimensions = array($width, $height);
811
812
        return $this;
813
    }
814
815
    /**
816
     * Configures the input and output instances based on the user arguments and options.
817
     *
818
     * @param InputInterface  $input  An InputInterface instance
819
     * @param OutputInterface $output An OutputInterface instance
820
     */
821
    protected function configureIO(InputInterface $input, OutputInterface $output)
822
    {
823
        if (true === $input->hasParameterOption(array('--ansi'))) {
824
            $output->setDecorated(true);
825
        } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
826
            $output->setDecorated(false);
827
        }
828
829
        if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
830
            $input->setInteractive(false);
831
        } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
832
            $inputStream = $this->getHelperSet()->get('question')->getInputStream();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method getInputStream() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\DialogHelper, Symfony\Component\Console\Helper\DialogHelper, Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Console\Helper\QuestionHelper.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
833
            if (!@posix_isatty($inputStream)) {
834
                $input->setInteractive(false);
835
            }
836
        }
837
838
        if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
839
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
840
        } else {
841
            if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
842
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
843
            } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
844
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
845
            } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
846
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
847
            }
848
        }
849
    }
850
851
    /**
852
     * Runs the current command.
853
     *
854
     * If an event dispatcher has been attached to the application,
855
     * events are also dispatched during the life-cycle of the command.
856
     *
857
     * @param Command         $command A Command instance
858
     * @param InputInterface  $input   An Input instance
859
     * @param OutputInterface $output  An Output instance
860
     *
861
     * @return int 0 if everything went fine, or an error code
862
     *
863
     * @throws \Exception when the command being run threw an exception
864
     */
865
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
866
    {
867
        foreach ($command->getHelperSet() as $helper) {
0 ignored issues
show
Bug introduced by
The expression $command->getHelperSet() of type object<Symfony\Component...\Helper\HelperSet>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
868
            if ($helper instanceof InputAwareInterface) {
869
                $helper->setInput($input);
870
            }
871
        }
872
873
        if (null === $this->dispatcher) {
874
            return $command->run($input, $output);
875
        }
876
877
        $event = new ConsoleCommandEvent($command, $input, $output);
878
        $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
879
880
        if ($event->commandShouldRun()) {
881
            try {
882
                $exitCode = $command->run($input, $output);
883
            } catch (\Exception $e) {
884
                $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
885
                $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
886
887
                $event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode());
888
                $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
889
890
                throw $event->getException();
891
            }
892
        } else {
893
            $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
894
        }
895
896
        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
897
        $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
898
899
        return $event->getExitCode();
900
    }
901
902
    /**
903
     * Gets the name of the command based on input.
904
     *
905
     * @param InputInterface $input The input interface
906
     *
907
     * @return string The command name
908
     */
909
    protected function getCommandName(InputInterface $input)
910
    {
911
        return $input->getFirstArgument();
912
    }
913
914
    /**
915
     * Gets the default input definition.
916
     *
917
     * @return InputDefinition An InputDefinition instance
918
     */
919
    protected function getDefaultInputDefinition()
920
    {
921
        return new InputDefinition(array(
922
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
923
924
            new InputOption('--help',           '-h', InputOption::VALUE_NONE, 'Display this help message'),
925
            new InputOption('--quiet',          '-q', InputOption::VALUE_NONE, 'Do not output any message'),
926
            new InputOption('--verbose',        '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
927
            new InputOption('--version',        '-V', InputOption::VALUE_NONE, 'Display this application version'),
928
            new InputOption('--ansi',           '',   InputOption::VALUE_NONE, 'Force ANSI output'),
929
            new InputOption('--no-ansi',        '',   InputOption::VALUE_NONE, 'Disable ANSI output'),
930
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
931
        ));
932
    }
933
934
    /**
935
     * Gets the default commands that should always be available.
936
     *
937
     * @return Command[] An array of default Command instances
938
     */
939
    protected function getDefaultCommands()
940
    {
941
        return array(new HelpCommand(), new ListCommand());
942
    }
943
944
    /**
945
     * Gets the default helper set with the helpers that should always be available.
946
     *
947
     * @return HelperSet A HelperSet instance
948
     */
949
    protected function getDefaultHelperSet()
950
    {
951
        return new HelperSet(array(
952
            new FormatterHelper(),
953
            new DialogHelper(),
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Console\Helper\DialogHelper has been deprecated with message: Deprecated since version 2.5, to be removed in 3.0. Use the question helper instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
954
            new ProgressHelper(),
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Console\Helper\ProgressHelper has been deprecated with message: Deprecated since 2.5, to be removed in 3.0; use ProgressBar instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
955
            new TableHelper(),
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Console\Helper\TableHelper has been deprecated with message: Deprecated since 2.5, to be removed in 3.0; use Table instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
956
            new DebugFormatterHelper(),
957
            new ProcessHelper(),
958
            new QuestionHelper(),
959
        ));
960
    }
961
962
    /**
963
     * Runs and parses stty -a if it's available, suppressing any error output.
964
     *
965
     * @return string
966
     */
967
    private function getSttyColumns()
968
    {
969
        if (!function_exists('proc_open')) {
970
            return;
971
        }
972
973
        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
974
        $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
975
        if (is_resource($process)) {
976
            $info = stream_get_contents($pipes[1]);
977
            fclose($pipes[1]);
978
            fclose($pipes[2]);
979
            proc_close($process);
980
981
            return $info;
982
        }
983
    }
984
985
    /**
986
     * Runs and parses mode CON if it's available, suppressing any error output.
987
     *
988
     * @return string <width>x<height> or null if it could not be parsed
989
     */
990
    private function getConsoleMode()
991
    {
992
        if (!function_exists('proc_open')) {
993
            return;
994
        }
995
996
        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
997
        $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
998
        if (is_resource($process)) {
999
            $info = stream_get_contents($pipes[1]);
1000
            fclose($pipes[1]);
1001
            fclose($pipes[2]);
1002
            proc_close($process);
1003
1004
            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
1005
                return $matches[2].'x'.$matches[1];
1006
            }
1007
        }
1008
    }
1009
1010
    /**
1011
     * Returns abbreviated suggestions in string format.
1012
     *
1013
     * @param array $abbrevs Abbreviated suggestions to convert
1014
     *
1015
     * @return string A formatted string of abbreviated suggestions
1016
     */
1017
    private function getAbbreviationSuggestions($abbrevs)
1018
    {
1019
        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
1020
    }
1021
1022
    /**
1023
     * Returns the namespace part of the command name.
1024
     *
1025
     * This method is not part of public API and should not be used directly.
1026
     *
1027
     * @param string $name  The full name of the command
1028
     * @param string $limit The maximum number of parts of the namespace
1029
     *
1030
     * @return string The namespace of the command
1031
     */
1032
    public function extractNamespace($name, $limit = null)
1033
    {
1034
        $parts = explode(':', $name);
1035
        array_pop($parts);
1036
1037
        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
1038
    }
1039
1040
    /**
1041
     * Finds alternative of $name among $collection,
1042
     * if nothing is found in $collection, try in $abbrevs.
1043
     *
1044
     * @param string             $name       The string
1045
     * @param array|\Traversable $collection The collection
1046
     *
1047
     * @return array A sorted array of similar string
1048
     */
1049
    private function findAlternatives($name, $collection)
1050
    {
1051
        $threshold = 1e3;
1052
        $alternatives = array();
1053
1054
        $collectionParts = array();
1055
        foreach ($collection as $item) {
1056
            $collectionParts[$item] = explode(':', $item);
1057
        }
1058
1059
        foreach (explode(':', $name) as $i => $subname) {
1060
            foreach ($collectionParts as $collectionName => $parts) {
1061
                $exists = isset($alternatives[$collectionName]);
1062
                if (!isset($parts[$i]) && $exists) {
1063
                    $alternatives[$collectionName] += $threshold;
1064
                    continue;
1065
                } elseif (!isset($parts[$i])) {
1066
                    continue;
1067
                }
1068
1069
                $lev = levenshtein($subname, $parts[$i]);
1070
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
1071
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
1072
                } elseif ($exists) {
1073
                    $alternatives[$collectionName] += $threshold;
1074
                }
1075
            }
1076
        }
1077
1078
        foreach ($collection as $item) {
1079
            $lev = levenshtein($name, $item);
1080
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1081
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
1082
            }
1083
        }
1084
1085
        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2*$threshold; });
1086
        asort($alternatives);
1087
1088
        return array_keys($alternatives);
1089
    }
1090
1091
    /**
1092
     * Sets the default Command name.
1093
     *
1094
     * @param string $commandName The Command name
1095
     */
1096
    public function setDefaultCommand($commandName)
1097
    {
1098
        $this->defaultCommand = $commandName;
1099
    }
1100
1101 View Code Duplication
    private function stringWidth($string)
1102
    {
1103
        if (!function_exists('mb_strwidth')) {
1104
            return strlen($string);
1105
        }
1106
1107
        if (false === $encoding = mb_detect_encoding($string)) {
1108
            return strlen($string);
1109
        }
1110
1111
        return mb_strwidth($string, $encoding);
1112
    }
1113
1114
    private function splitStringByWidth($string, $width)
1115
    {
1116
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1117
        // additionally, array_slice() is not enough as some character has doubled width.
1118
        // we need a function to split string not by character count but by string width
1119
1120
        if (!function_exists('mb_strwidth')) {
1121
            return str_split($string, $width);
1122
        }
1123
1124
        if (false === $encoding = mb_detect_encoding($string)) {
1125
            return str_split($string, $width);
1126
        }
1127
1128
        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1129
        $lines = array();
1130
        $line = '';
1131
        foreach (preg_split('//u', $utf8String) as $char) {
1132
            // test if $char could be appended to current line
1133
            if (mb_strwidth($line.$char, 'utf8') <= $width) {
1134
                $line .= $char;
1135
                continue;
1136
            }
1137
            // if not, push current line to array and make new line
1138
            $lines[] = str_pad($line, $width);
1139
            $line = $char;
1140
        }
1141
        if (strlen($line)) {
1142
            $lines[] = count($lines) ? str_pad($line, $width) : $line;
1143
        }
1144
1145
        mb_convert_variables($encoding, 'utf8', $lines);
1146
1147
        return $lines;
1148
    }
1149
1150
    /**
1151
     * Returns all namespaces of the command name.
1152
     *
1153
     * @param string $name The full name of the command
1154
     *
1155
     * @return array The namespaces of the command
1156
     */
1157
    private function extractAllNamespaces($name)
1158
    {
1159
        // -1 as third argument is needed to skip the command short name when exploding
1160
        $parts = explode(':', $name, -1);
1161
        $namespaces = array();
1162
1163
        foreach ($parts as $part) {
1164
            if (count($namespaces)) {
1165
                $namespaces[] = end($namespaces).':'.$part;
1166
            } else {
1167
                $namespaces[] = $part;
1168
            }
1169
        }
1170
1171
        return $namespaces;
1172
    }
1173
}
1174