Completed
Push — master ( 96d573...f9f049 )
by Ehsan
07:54
created

Application   F

Complexity

Total Complexity 179

Size/Duplication

Total Lines 1078
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 30

Importance

Changes 0
Metric Value
dl 0
loc 1078
rs 0.6365
c 0
b 0
f 0
wmc 179
lcom 1
cbo 30

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 2
F run() 0 60 15
C doRun() 0 50 10
A setHelperSet() 0 4 1
A getHelperSet() 0 4 1
A getDefinition() 0 11 2
A getHelp() 0 4 1
A setCatchExceptions() 0 4 1
A setAutoExit() 0 4 1
A getName() 0 4 1
A setName() 0 4 1
A getVersion() 0 4 1
A setVersion() 0 4 1
A getLongVersion() 0 12 3
A register() 0 4 1
A addCommands() 0 6 2
B add() 0 22 4
A get() 0 19 3
A has() 0 4 1
A getNamespaces() 0 13 3
C findNamespace() 0 29 7
C find() 0 56 13
A all() 0 15 4
A getAbbreviations() 0 12 3
C renderException() 0 71 19
A getCommandName() 0 4 2
A getAbbreviationSuggestions() 0 4 1
A extractNamespace() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Application often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Application, and based on these observations, apply Extract Interface, too.

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\Exception\ExceptionInterface;
15
use Symfony\Component\Console\Formatter\OutputFormatter;
16
use Symfony\Component\Console\Helper\DebugFormatterHelper;
17
use Symfony\Component\Console\Helper\Helper;
18
use Symfony\Component\Console\Helper\ProcessHelper;
19
use Symfony\Component\Console\Helper\QuestionHelper;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\StreamableInputInterface;
22
use Symfony\Component\Console\Input\ArgvInput;
23
use Symfony\Component\Console\Input\ArrayInput;
24
use Symfony\Component\Console\Input\InputDefinition;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Input\InputArgument;
27
use Symfony\Component\Console\Input\InputAwareInterface;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Symfony\Component\Console\Output\ConsoleOutput;
30
use Symfony\Component\Console\Output\ConsoleOutputInterface;
31
use Symfony\Component\Console\Command\Command;
32
use Symfony\Component\Console\Command\HelpCommand;
33
use Symfony\Component\Console\Command\ListCommand;
34
use Symfony\Component\Console\Helper\HelperSet;
35
use Symfony\Component\Console\Helper\FormatterHelper;
36
use Symfony\Component\Console\Event\ConsoleCommandEvent;
37
use Symfony\Component\Console\Event\ConsoleErrorEvent;
38
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
39
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
40
use Symfony\Component\Console\Exception\CommandNotFoundException;
41
use Symfony\Component\Console\Exception\LogicException;
42
use Symfony\Component\Debug\Exception\FatalThrowableError;
43
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
44
45
/**
46
 * An Application is the container for a collection of commands.
47
 *
48
 * It is the main entry point of a Console application.
49
 *
50
 * This class is optimized for a standard CLI environment.
51
 *
52
 * Usage:
53
 *
54
 *     $app = new Application('myapp', '1.0 (stable)');
55
 *     $app->add(new SimpleCommand());
56
 *     $app->run();
57
 *
58
 * @author Fabien Potencier <[email protected]>
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 $terminal;
73
    private $defaultCommand;
74
    private $singleCommand;
75
76
    /**
77
     * @param string $name    The name of the application
78
     * @param string $version The version of the application
79
     */
80
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
81
    {
82
        $this->name = $name;
83
        $this->version = $version;
84
        $this->terminal = new Terminal();
85
        $this->defaultCommand = 'list';
86
        $this->helperSet = $this->getDefaultHelperSet();
87
        $this->definition = $this->getDefaultInputDefinition();
88
89
        foreach ($this->getDefaultCommands() as $command) {
90
            $this->add($command);
91
        }
92
    }
93
94
    public function setDispatcher(EventDispatcherInterface $dispatcher)
95
    {
96
        $this->dispatcher = $dispatcher;
97
    }
98
99
    /**
100
     * Runs the current application.
101
     *
102
     * @param InputInterface  $input  An Input instance
103
     * @param OutputInterface $output An Output instance
104
     *
105
     * @return int 0 if everything went fine, or an error code
106
     *
107
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
108
     */
109
    public function run(InputInterface $input = null, OutputInterface $output = null)
110
    {
111
        putenv('LINES='.$this->terminal->getHeight());
112
        putenv('COLUMNS='.$this->terminal->getWidth());
113
114
        if (null === $input) {
115
            $input = new ArgvInput();
116
        }
117
118
        if (null === $output) {
119
            $output = new ConsoleOutput();
120
        }
121
122
        if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Console\ConsoleEvents::EXCEPTION has been deprecated with message: The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
123
            @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
124
        }
125
126
        $this->configureIO($input, $output);
127
128
        try {
129
            $e = null;
130
            $exitCode = $this->doRun($input, $output);
131
        } catch (\Exception $x) {
132
            $e = $x;
133
        } catch (\Throwable $x) {
134
            $e = new FatalThrowableError($x);
135
        }
136
137
        if (null !== $e) {
138
            if (!$this->catchExceptions || !$x instanceof \Exception) {
0 ignored issues
show
Bug introduced by
The variable $x does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
139
                throw $x;
140
            }
141
142
            if ($output instanceof ConsoleOutputInterface) {
143
                $this->renderException($e, $output->getErrorOutput());
144
            } else {
145
                $this->renderException($e, $output);
146
            }
147
148
            $exitCode = $e->getCode();
0 ignored issues
show
Bug introduced by
The method getCode does only exist in Exception, but not in Symfony\Component\Debug\...ion\FatalThrowableError.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
149
            if (is_numeric($exitCode)) {
150
                $exitCode = (int) $exitCode;
151
                if (0 === $exitCode) {
152
                    $exitCode = 1;
153
                }
154
            } else {
155
                $exitCode = 1;
156
            }
157
        }
158
159
        if ($this->autoExit) {
160
            if ($exitCode > 255) {
161
                $exitCode = 255;
162
            }
163
164
            exit($exitCode);
165
        }
166
167
        return $exitCode;
168
    }
169
170
    /**
171
     * Runs the current application.
172
     *
173
     * @param InputInterface  $input  An Input instance
174
     * @param OutputInterface $output An Output instance
175
     *
176
     * @return int 0 if everything went fine, or an error code
177
     */
178
    public function doRun(InputInterface $input, OutputInterface $output)
179
    {
180
        if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
181
            $output->writeln($this->getLongVersion());
182
183
            return 0;
184
        }
185
186
        $name = $this->getCommandName($input);
187
        if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
188
            if (!$name) {
189
                $name = 'help';
190
                $input = new ArrayInput(array('command_name' => $this->defaultCommand));
191
            } else {
192
                $this->wantHelps = true;
193
            }
194
        }
195
196
        if (!$name) {
197
            $name = $this->defaultCommand;
198
            $input = new ArrayInput(array('command' => $this->defaultCommand));
199
        }
200
201
        try {
202
            $e = $this->runningCommand = null;
203
            // the command name MUST be the first element of the input
204
            $command = $this->find($name);
205
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
206
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
207
        }
208
        if (null !== $e) {
209
            if (null !== $this->dispatcher) {
210
                $event = new ConsoleErrorEvent($input, $output, $e);
211
                $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
212
                $e = $event->getError();
213
214
                if (0 === $event->getExitCode()) {
215
                    return 0;
216
                }
217
            }
218
219
            throw $e;
220
        }
221
222
        $this->runningCommand = $command;
223
        $exitCode = $this->doRunCommand($command, $input, $output);
224
        $this->runningCommand = null;
225
226
        return $exitCode;
227
    }
228
229
    /**
230
     * Set a helper set to be used with the command.
231
     *
232
     * @param HelperSet $helperSet The helper set
233
     */
234
    public function setHelperSet(HelperSet $helperSet)
235
    {
236
        $this->helperSet = $helperSet;
237
    }
238
239
    /**
240
     * Get the helper set associated with the command.
241
     *
242
     * @return HelperSet The HelperSet instance associated with this command
243
     */
244
    public function getHelperSet()
245
    {
246
        return $this->helperSet;
247
    }
248
249
    /**
250
     * Set an input definition to be used with this application.
251
     *
252
     * @param InputDefinition $definition The input definition
253
     */
254
    public function setDefinition(InputDefinition $definition)
255
    {
256
        $this->definition = $definition;
257
    }
258
259
    /**
260
     * Gets the InputDefinition related to this Application.
261
     *
262
     * @return InputDefinition The InputDefinition instance
263
     */
264
    public function getDefinition()
265
    {
266
        if ($this->singleCommand) {
267
            $inputDefinition = $this->definition;
268
            $inputDefinition->setArguments();
269
270
            return $inputDefinition;
271
        }
272
273
        return $this->definition;
274
    }
275
276
    /**
277
     * Gets the help message.
278
     *
279
     * @return string A help message
280
     */
281
    public function getHelp()
282
    {
283
        return $this->getLongVersion();
284
    }
285
286
    /**
287
     * Gets whether to catch exceptions or not during commands execution.
288
     *
289
     * @return bool Whether to catch exceptions or not during commands execution
290
     */
291
    public function areExceptionsCaught()
292
    {
293
        return $this->catchExceptions;
294
    }
295
296
    /**
297
     * Sets whether to catch exceptions or not during commands execution.
298
     *
299
     * @param bool $boolean Whether to catch exceptions or not during commands execution
300
     */
301
    public function setCatchExceptions($boolean)
302
    {
303
        $this->catchExceptions = (bool) $boolean;
304
    }
305
306
    /**
307
     * Gets whether to automatically exit after a command execution or not.
308
     *
309
     * @return bool Whether to automatically exit after a command execution or not
310
     */
311
    public function isAutoExitEnabled()
312
    {
313
        return $this->autoExit;
314
    }
315
316
    /**
317
     * Sets whether to automatically exit after a command execution or not.
318
     *
319
     * @param bool $boolean Whether to automatically exit after a command execution or not
320
     */
321
    public function setAutoExit($boolean)
322
    {
323
        $this->autoExit = (bool) $boolean;
324
    }
325
326
    /**
327
     * Gets the name of the application.
328
     *
329
     * @return string The application name
330
     */
331
    public function getName()
332
    {
333
        return $this->name;
334
    }
335
336
    /**
337
     * Sets the application name.
338
     *
339
     * @param string $name The application name
340
     */
341
    public function setName($name)
342
    {
343
        $this->name = $name;
344
    }
345
346
    /**
347
     * Gets the application version.
348
     *
349
     * @return string The application version
350
     */
351
    public function getVersion()
352
    {
353
        return $this->version;
354
    }
355
356
    /**
357
     * Sets the application version.
358
     *
359
     * @param string $version The application version
360
     */
361
    public function setVersion($version)
362
    {
363
        $this->version = $version;
364
    }
365
366
    /**
367
     * Returns the long version of the application.
368
     *
369
     * @return string The long application version
370
     */
371
    public function getLongVersion()
372
    {
373
        if ('UNKNOWN' !== $this->getName()) {
374
            if ('UNKNOWN' !== $this->getVersion()) {
375
                return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
376
            }
377
378
            return $this->getName();
379
        }
380
381
        return 'Console Tool';
382
    }
383
384
    /**
385
     * Registers a new command.
386
     *
387
     * @param string $name The command name
388
     *
389
     * @return Command The newly created command
390
     */
391
    public function register($name)
392
    {
393
        return $this->add(new Command($name));
394
    }
395
396
    /**
397
     * Adds an array of command objects.
398
     *
399
     * If a Command is not enabled it will not be added.
400
     *
401
     * @param Command[] $commands An array of commands
402
     */
403
    public function addCommands(array $commands)
404
    {
405
        foreach ($commands as $command) {
406
            $this->add($command);
407
        }
408
    }
409
410
    /**
411
     * Adds a command object.
412
     *
413
     * If a command with the same name already exists, it will be overridden.
414
     * If the command is not enabled it will not be added.
415
     *
416
     * @param Command $command A Command object
417
     *
418
     * @return Command|null The registered command if enabled or null
419
     */
420
    public function add(Command $command)
421
    {
422
        $command->setApplication($this);
423
424
        if (!$command->isEnabled()) {
425
            $command->setApplication(null);
426
427
            return;
428
        }
429
430
        if (null === $command->getDefinition()) {
431
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
432
        }
433
434
        $this->commands[$command->getName()] = $command;
435
436
        foreach ($command->getAliases() as $alias) {
437
            $this->commands[$alias] = $command;
438
        }
439
440
        return $command;
441
    }
442
443
    /**
444
     * Returns a registered command by name or alias.
445
     *
446
     * @param string $name The command name or alias
447
     *
448
     * @return Command A Command object
449
     *
450
     * @throws CommandNotFoundException When given command name does not exist
451
     */
452
    public function get($name)
453
    {
454
        if (!isset($this->commands[$name])) {
455
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
456
        }
457
458
        $command = $this->commands[$name];
459
460
        if ($this->wantHelps) {
461
            $this->wantHelps = false;
462
463
            $helpCommand = $this->get('help');
464
            $helpCommand->setCommand($command);
465
466
            return $helpCommand;
467
        }
468
469
        return $command;
470
    }
471
472
    /**
473
     * Returns true if the command exists, false otherwise.
474
     *
475
     * @param string $name The command name or alias
476
     *
477
     * @return bool true if the command exists, false otherwise
478
     */
479
    public function has($name)
480
    {
481
        return isset($this->commands[$name]);
482
    }
483
484
    /**
485
     * Returns an array of all unique namespaces used by currently registered commands.
486
     *
487
     * It does not return the global namespace which always exists.
488
     *
489
     * @return string[] An array of namespaces
490
     */
491
    public function getNamespaces()
492
    {
493
        $namespaces = array();
494
        foreach ($this->all() as $command) {
495
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
496
497
            foreach ($command->getAliases() as $alias) {
498
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
499
            }
500
        }
501
502
        return array_values(array_unique(array_filter($namespaces)));
503
    }
504
505
    /**
506
     * Finds a registered namespace by a name or an abbreviation.
507
     *
508
     * @param string $namespace A namespace or abbreviation to search for
509
     *
510
     * @return string A registered namespace
511
     *
512
     * @throws CommandNotFoundException When namespace is incorrect or ambiguous
513
     */
514
    public function findNamespace($namespace)
515
    {
516
        $allNamespaces = $this->getNamespaces();
517
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
518
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
519
520
        if (empty($namespaces)) {
521
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
522
523
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
524
                if (1 == count($alternatives)) {
525
                    $message .= "\n\nDid you mean this?\n    ";
526
                } else {
527
                    $message .= "\n\nDid you mean one of these?\n    ";
528
                }
529
530
                $message .= implode("\n    ", $alternatives);
531
            }
532
533
            throw new CommandNotFoundException($message, $alternatives);
534
        }
535
536
        $exact = in_array($namespace, $namespaces, true);
537
        if (count($namespaces) > 1 && !$exact) {
538
            throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
539
        }
540
541
        return $exact ? $namespace : reset($namespaces);
542
    }
543
544
    /**
545
     * Finds a command by name or alias.
546
     *
547
     * Contrary to get, this command tries to find the best
548
     * match if you give it an abbreviation of a name or alias.
549
     *
550
     * @param string $name A command name or a command alias
551
     *
552
     * @return Command A Command instance
553
     *
554
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
555
     */
556
    public function find($name)
557
    {
558
        $allCommands = array_keys($this->commands);
559
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
560
        $commands = preg_grep('{^'.$expr.'}', $allCommands);
561
562
        if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
563
            if (false !== $pos = strrpos($name, ':')) {
564
                // check if a namespace exists and contains commands
565
                $this->findNamespace(substr($name, 0, $pos));
566
            }
567
568
            $message = sprintf('Command "%s" is not defined.', $name);
569
570
            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
571
                if (1 == count($alternatives)) {
572
                    $message .= "\n\nDid you mean this?\n    ";
573
                } else {
574
                    $message .= "\n\nDid you mean one of these?\n    ";
575
                }
576
                $message .= implode("\n    ", $alternatives);
577
            }
578
579
            throw new CommandNotFoundException($message, $alternatives);
580
        }
581
582
        // filter out aliases for commands which are already on the list
583
        if (count($commands) > 1) {
584
            $commandList = $this->commands;
585
            $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
586
                $commandName = $commandList[$nameOrAlias]->getName();
587
588
                return $commandName === $nameOrAlias || !in_array($commandName, $commands);
589
            });
590
        }
591
592
        $exact = in_array($name, $commands, true);
593
        if (count($commands) > 1 && !$exact) {
594
            $usableWidth = $this->terminal->getWidth() - 10;
595
            $abbrevs = array_values($commands);
596
            $maxLen = 0;
597
            foreach ($abbrevs as $abbrev) {
598
                $maxLen = max(Helper::strlen($abbrev), $maxLen);
599
            }
600
            $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
0 ignored issues
show
Bug introduced by
The variable $commandList does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
601
                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
602
603
                return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
604
            }, array_values($commands));
605
            $suggestions = $this->getAbbreviationSuggestions($abbrevs);
606
607
            throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
608
        }
609
610
        return $this->get($exact ? $name : reset($commands));
611
    }
612
613
    /**
614
     * Gets the commands (registered in the given namespace if provided).
615
     *
616
     * The array keys are the full names and the values the command instances.
617
     *
618
     * @param string $namespace A namespace name
619
     *
620
     * @return Command[] An array of Command instances
621
     */
622
    public function all($namespace = null)
623
    {
624
        if (null === $namespace) {
625
            return $this->commands;
626
        }
627
628
        $commands = array();
629
        foreach ($this->commands as $name => $command) {
630
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
631
                $commands[$name] = $command;
632
            }
633
        }
634
635
        return $commands;
636
    }
637
638
    /**
639
     * Returns an array of possible abbreviations given a set of names.
640
     *
641
     * @param array $names An array of names
642
     *
643
     * @return array An array of abbreviations
644
     */
645
    public static function getAbbreviations($names)
646
    {
647
        $abbrevs = array();
648
        foreach ($names as $name) {
649
            for ($len = strlen($name); $len > 0; --$len) {
650
                $abbrev = substr($name, 0, $len);
651
                $abbrevs[$abbrev][] = $name;
652
            }
653
        }
654
655
        return $abbrevs;
656
    }
657
658
    /**
659
     * Renders a caught exception.
660
     *
661
     * @param \Exception      $e      An exception instance
662
     * @param OutputInterface $output An OutputInterface instance
663
     */
664
    public function renderException(\Exception $e, OutputInterface $output)
665
    {
666
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);
667
668
        do {
669
            $title = sprintf(
670
                '  [%s%s]  ',
671
                get_class($e),
672
                $output->isVerbose() && 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''
0 ignored issues
show
Bug introduced by
The variable $code does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
673
            );
674
675
            $len = Helper::strlen($title);
676
677
            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
678
            // 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
679
            if (defined('HHVM_VERSION') && $width > 1 << 31) {
680
                $width = 1 << 31;
681
            }
682
            $lines = array();
683
            foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
684
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
685
                    // pre-format lines to get the right string length
686
                    $lineLength = Helper::strlen($line) + 4;
687
                    $lines[] = array($line, $lineLength);
688
689
                    $len = max($lineLength, $len);
690
                }
691
            }
692
693
            $messages = array();
694
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
695
            $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
696
            foreach ($lines as $line) {
697
                $messages[] = sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
698
            }
699
            $messages[] = $emptyLine;
700
            $messages[] = '';
701
702
            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
703
704
            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
705
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
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), OutputInterface::VERBOSITY_QUIET);
724
                }
725
726
                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
727
            }
728
        } while ($e = $e->getPrevious());
729
730
        if (null !== $this->runningCommand) {
731
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
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...
732
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
733
        }
734
    }
735
736
    /**
737
     * Tries to figure out the terminal width in which this application runs.
738
     *
739
     * @return int|null
740
     *
741
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
742
     */
743
    protected function getTerminalWidth()
744
    {
745
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
746
747
        return $this->terminal->getWidth();
748
    }
749
750
    /**
751
     * Tries to figure out the terminal height in which this application runs.
752
     *
753
     * @return int|null
754
     *
755
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
756
     */
757
    protected function getTerminalHeight()
758
    {
759
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
760
761
        return $this->terminal->getHeight();
762
    }
763
764
    /**
765
     * Tries to figure out the terminal dimensions based on the current environment.
766
     *
767
     * @return array Array containing width and height
768
     *
769
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
770
     */
771
    public function getTerminalDimensions()
772
    {
773
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
774
775
        return array($this->terminal->getWidth(), $this->terminal->getHeight());
776
    }
777
778
    /**
779
     * Sets terminal dimensions.
780
     *
781
     * Can be useful to force terminal dimensions for functional tests.
782
     *
783
     * @param int $width  The width
784
     * @param int $height The height
785
     *
786
     * @return $this
787
     *
788
     * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead.
789
     */
790
    public function setTerminalDimensions($width, $height)
791
    {
792
        @trigger_error(sprintf('%s is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
793
794
        putenv('COLUMNS='.$width);
795
        putenv('LINES='.$height);
796
797
        return $this;
798
    }
799
800
    /**
801
     * Configures the input and output instances based on the user arguments and options.
802
     *
803
     * @param InputInterface  $input  An InputInterface instance
804
     * @param OutputInterface $output An OutputInterface instance
805
     */
806
    protected function configureIO(InputInterface $input, OutputInterface $output)
807
    {
808
        if (true === $input->hasParameterOption(array('--ansi'), true)) {
809
            $output->setDecorated(true);
810
        } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
811
            $output->setDecorated(false);
812
        }
813
814
        if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
815
            $input->setInteractive(false);
816
        } elseif (function_exists('posix_isatty')) {
817
            $inputStream = null;
818
819
            if ($input instanceof StreamableInputInterface) {
820
                $inputStream = $input->getStream();
821
            }
822
823
            // This check ensures that calling QuestionHelper::setInputStream() works
824
            // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream)
825
            if (!$inputStream && $this->getHelperSet()->has('question')) {
826
                $inputStream = $this->getHelperSet()->get('question')->getInputStream(false);
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\QuestionHelper, Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.

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...
827
            }
828
829
            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
830
                $input->setInteractive(false);
831
            }
832
        }
833
834
        if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
835
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
836
            $input->setInteractive(false);
837
        } else {
838
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) {
839
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
840
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) {
841
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
842
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
843
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
844
            }
845
        }
846
    }
847
848
    /**
849
     * Runs the current command.
850
     *
851
     * If an event dispatcher has been attached to the application,
852
     * events are also dispatched during the life-cycle of the command.
853
     *
854
     * @param Command         $command A Command instance
855
     * @param InputInterface  $input   An Input instance
856
     * @param OutputInterface $output  An Output instance
857
     *
858
     * @return int 0 if everything went fine, or an error code
859
     */
860
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
861
    {
862
        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...
863
            if ($helper instanceof InputAwareInterface) {
864
                $helper->setInput($input);
865
            }
866
        }
867
868
        if (null === $this->dispatcher) {
869
            return $command->run($input, $output);
870
        }
871
872
        // bind before the console.command event, so the listeners have access to input options/arguments
873
        try {
874
            $command->mergeApplicationDefinition();
875
            $input->bind($command->getDefinition());
876
        } catch (ExceptionInterface $e) {
877
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
878
        }
879
880
        $event = new ConsoleCommandEvent($command, $input, $output);
881
        $e = null;
882
883
        try {
884
            $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
885
886
            if ($event->commandShouldRun()) {
887
                $exitCode = $command->run($input, $output);
888
            } else {
889
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
890
            }
891
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
892
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
893
        }
894
        if (null !== $e) {
895
            if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Console\ConsoleEvents::EXCEPTION has been deprecated with message: The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
896
                $x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
897
                $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
0 ignored issues
show
Bug introduced by
The method getCode does only exist in Exception, but not in Symfony\Component\Debug\...ion\FatalThrowableError.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Deprecated Code introduced by
The class Symfony\Component\Consol...t\ConsoleExceptionEvent has been deprecated with message: since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent 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...
898
                $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Console\ConsoleEvents::EXCEPTION has been deprecated with message: The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
899
900
                if ($x !== $event->getException()) {
901
                    $e = $event->getException();
902
                }
903
            }
904
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
905
            $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
906
            $e = $event->getError();
907
908
            if (0 === $exitCode = $event->getExitCode()) {
909
                $e = null;
910
            }
911
        }
912
913
        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
0 ignored issues
show
Bug introduced by
The variable $exitCode does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
914
        $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
915
916
        if (null !== $e) {
917
            throw $e;
918
        }
919
920
        return $event->getExitCode();
921
    }
922
923
    /**
924
     * Gets the name of the command based on input.
925
     *
926
     * @param InputInterface $input The input interface
927
     *
928
     * @return string The command name
929
     */
930
    protected function getCommandName(InputInterface $input)
931
    {
932
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
933
    }
934
935
    /**
936
     * Gets the default input definition.
937
     *
938
     * @return InputDefinition An InputDefinition instance
939
     */
940
    protected function getDefaultInputDefinition()
941
    {
942
        return new InputDefinition(array(
943
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
944
945
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
946
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
947
            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'),
948
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
949
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
950
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
951
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
952
        ));
953
    }
954
955
    /**
956
     * Gets the default commands that should always be available.
957
     *
958
     * @return Command[] An array of default Command instances
959
     */
960
    protected function getDefaultCommands()
961
    {
962
        return array(new HelpCommand(), new ListCommand());
963
    }
964
965
    /**
966
     * Gets the default helper set with the helpers that should always be available.
967
     *
968
     * @return HelperSet A HelperSet instance
969
     */
970
    protected function getDefaultHelperSet()
971
    {
972
        return new HelperSet(array(
973
            new FormatterHelper(),
974
            new DebugFormatterHelper(),
975
            new ProcessHelper(),
976
            new QuestionHelper(),
977
        ));
978
    }
979
980
    /**
981
     * Returns abbreviated suggestions in string format.
982
     *
983
     * @param array $abbrevs Abbreviated suggestions to convert
984
     *
985
     * @return string A formatted string of abbreviated suggestions
986
     */
987
    private function getAbbreviationSuggestions($abbrevs)
988
    {
989
        return '    '.implode("\n    ", $abbrevs);
990
    }
991
992
    /**
993
     * Returns the namespace part of the command name.
994
     *
995
     * This method is not part of public API and should not be used directly.
996
     *
997
     * @param string $name  The full name of the command
998
     * @param string $limit The maximum number of parts of the namespace
999
     *
1000
     * @return string The namespace of the command
1001
     */
1002
    public function extractNamespace($name, $limit = null)
1003
    {
1004
        $parts = explode(':', $name);
1005
        array_pop($parts);
1006
1007
        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
1008
    }
1009
1010
    /**
1011
     * Finds alternative of $name among $collection,
1012
     * if nothing is found in $collection, try in $abbrevs.
1013
     *
1014
     * @param string             $name       The string
1015
     * @param array|\Traversable $collection The collection
1016
     *
1017
     * @return string[] A sorted array of similar string
1018
     */
1019
    private function findAlternatives($name, $collection)
1020
    {
1021
        $threshold = 1e3;
1022
        $alternatives = array();
1023
1024
        $collectionParts = array();
1025
        foreach ($collection as $item) {
1026
            $collectionParts[$item] = explode(':', $item);
1027
        }
1028
1029
        foreach (explode(':', $name) as $i => $subname) {
1030
            foreach ($collectionParts as $collectionName => $parts) {
1031
                $exists = isset($alternatives[$collectionName]);
1032
                if (!isset($parts[$i]) && $exists) {
1033
                    $alternatives[$collectionName] += $threshold;
1034
                    continue;
1035
                } elseif (!isset($parts[$i])) {
1036
                    continue;
1037
                }
1038
1039
                $lev = levenshtein($subname, $parts[$i]);
1040
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
1041
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
1042
                } elseif ($exists) {
1043
                    $alternatives[$collectionName] += $threshold;
1044
                }
1045
            }
1046
        }
1047
1048
        foreach ($collection as $item) {
1049
            $lev = levenshtein($name, $item);
1050
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1051
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
1052
            }
1053
        }
1054
1055
        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
1056
        ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
1057
1058
        return array_keys($alternatives);
1059
    }
1060
1061
    /**
1062
     * Sets the default Command name.
1063
     *
1064
     * @param string $commandName     The Command name
1065
     * @param bool   $isSingleCommand Set to true if there is only one command in this application
1066
     *
1067
     * @return self
1068
     */
1069
    public function setDefaultCommand($commandName, $isSingleCommand = false)
1070
    {
1071
        $this->defaultCommand = $commandName;
1072
1073
        if ($isSingleCommand) {
1074
            // Ensure the command exist
1075
            $this->find($commandName);
1076
1077
            $this->singleCommand = true;
1078
        }
1079
1080
        return $this;
1081
    }
1082
1083
    private function splitStringByWidth($string, $width)
1084
    {
1085
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1086
        // additionally, array_slice() is not enough as some character has doubled width.
1087
        // we need a function to split string not by character count but by string width
1088
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
1089
            return str_split($string, $width);
1090
        }
1091
1092
        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1093
        $lines = array();
1094
        $line = '';
1095
        foreach (preg_split('//u', $utf8String) as $char) {
1096
            // test if $char could be appended to current line
1097
            if (mb_strwidth($line.$char, 'utf8') <= $width) {
1098
                $line .= $char;
1099
                continue;
1100
            }
1101
            // if not, push current line to array and make new line
1102
            $lines[] = str_pad($line, $width);
1103
            $line = $char;
1104
        }
1105
        if ('' !== $line) {
1106
            $lines[] = count($lines) ? str_pad($line, $width) : $line;
1107
        }
1108
1109
        mb_convert_variables($encoding, 'utf8', $lines);
1110
1111
        return $lines;
1112
    }
1113
1114
    /**
1115
     * Returns all namespaces of the command name.
1116
     *
1117
     * @param string $name The full name of the command
1118
     *
1119
     * @return string[] The namespaces of the command
1120
     */
1121
    private function extractAllNamespaces($name)
1122
    {
1123
        // -1 as third argument is needed to skip the command short name when exploding
1124
        $parts = explode(':', $name, -1);
1125
        $namespaces = array();
1126
1127
        foreach ($parts as $part) {
1128
            if (count($namespaces)) {
1129
                $namespaces[] = end($namespaces).':'.$part;
1130
            } else {
1131
                $namespaces[] = $part;
1132
            }
1133
        }
1134
1135
        return $namespaces;
1136
    }
1137
}
1138