Application   F
last analyzed

Complexity

Total Complexity 230

Size/Duplication

Total Lines 1166
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 230
eloc 483
c 1
b 0
f 0
dl 0
loc 1166
rs 2

48 Methods

Rating   Name   Duplication   Size   Complexity  
F find() 0 101 22
A areExceptionsCaught() 0 3 1
C all() 0 35 13
A getVersion() 0 3 1
A isSingleCommand() 0 3 1
C doRun() 0 79 15
A isAutoExitEnabled() 0 3 1
A addCommands() 0 4 2
A getDefinition() 0 14 3
A setCatchExceptions() 0 3 1
A setDispatcher() 0 3 1
A getHelperSet() 0 7 2
A getDefaultCommands() 0 3 1
A setDefaultCommand() 0 12 2
A getAbbreviationSuggestions() 0 3 1
A has() 0 5 4
A getAbbreviations() 0 11 3
F doRenderThrowable() 0 72 25
A setHelperSet() 0 3 1
A getHelp() 0 3 1
A setAutoExit() 0 3 1
A getDefaultHelperSet() 0 7 1
A getNamespaces() 0 16 4
A setVersion() 0 3 1
C findAlternatives() 0 40 16
A reset() 0 2 1
A renderThrowable() 0 9 2
A getSignalRegistry() 0 7 2
B findNamespace() 0 28 7
F run() 0 76 19
A setDefinition() 0 3 1
F configureIO() 0 45 21
A getDefaultInputDefinition() 0 10 1
F doRunCommand() 0 76 16
A init() 0 9 3
A getName() 0 3 1
A setCommandLoader() 0 3 1
A splitStringByWidth() 0 34 6
A get() 0 25 4
A __construct() 0 9 3
A setSignalsToDispatchEvent() 0 3 1
A getCommandName() 0 3 2
A register() 0 3 1
A getLongVersion() 0 11 3
A extractNamespace() 0 5 2
A add() 0 28 5
A setName() 0 3 1
A extractAllNamespaces() 0 15 3

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.

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\Command\Command;
15
use Symfony\Component\Console\Command\HelpCommand;
16
use Symfony\Component\Console\Command\LazyCommand;
17
use Symfony\Component\Console\Command\ListCommand;
18
use Symfony\Component\Console\Command\SignalableCommandInterface;
19
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
20
use Symfony\Component\Console\Event\ConsoleCommandEvent;
21
use Symfony\Component\Console\Event\ConsoleErrorEvent;
22
use Symfony\Component\Console\Event\ConsoleSignalEvent;
23
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
24
use Symfony\Component\Console\Exception\CommandNotFoundException;
25
use Symfony\Component\Console\Exception\ExceptionInterface;
26
use Symfony\Component\Console\Exception\LogicException;
27
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
28
use Symfony\Component\Console\Exception\RuntimeException;
29
use Symfony\Component\Console\Formatter\OutputFormatter;
30
use Symfony\Component\Console\Helper\DebugFormatterHelper;
31
use Symfony\Component\Console\Helper\FormatterHelper;
32
use Symfony\Component\Console\Helper\Helper;
33
use Symfony\Component\Console\Helper\HelperSet;
34
use Symfony\Component\Console\Helper\ProcessHelper;
35
use Symfony\Component\Console\Helper\QuestionHelper;
36
use Symfony\Component\Console\Input\ArgvInput;
37
use Symfony\Component\Console\Input\ArrayInput;
38
use Symfony\Component\Console\Input\InputArgument;
39
use Symfony\Component\Console\Input\InputAwareInterface;
40
use Symfony\Component\Console\Input\InputDefinition;
41
use Symfony\Component\Console\Input\InputInterface;
42
use Symfony\Component\Console\Input\InputOption;
43
use Symfony\Component\Console\Output\ConsoleOutput;
44
use Symfony\Component\Console\Output\ConsoleOutputInterface;
45
use Symfony\Component\Console\Output\OutputInterface;
46
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
47
use Symfony\Component\Console\Style\SymfonyStyle;
48
use Symfony\Component\ErrorHandler\ErrorHandler;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\ErrorHandler\ErrorHandler was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
49
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
50
use Symfony\Contracts\Service\ResetInterface;
51
52
/**
53
 * An Application is the container for a collection of commands.
54
 *
55
 * It is the main entry point of a Console application.
56
 *
57
 * This class is optimized for a standard CLI environment.
58
 *
59
 * Usage:
60
 *
61
 *     $app = new Application('myapp', '1.0 (stable)');
62
 *     $app->add(new SimpleCommand());
63
 *     $app->run();
64
 *
65
 * @author Fabien Potencier <[email protected]>
66
 */
67
class Application implements ResetInterface
68
{
69
    private $commands = [];
70
    private $wantHelps = false;
71
    private $runningCommand;
72
    private $name;
73
    private $version;
74
    private $commandLoader;
75
    private $catchExceptions = true;
76
    private $autoExit = true;
77
    private $definition;
78
    private $helperSet;
79
    private $dispatcher;
80
    private $terminal;
81
    private $defaultCommand;
82
    private $singleCommand = false;
83
    private $initialized;
84
    private $signalRegistry;
85
    private $signalsToDispatchEvent = [];
86
87
    public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
88
    {
89
        $this->name = $name;
90
        $this->version = $version;
91
        $this->terminal = new Terminal();
92
        $this->defaultCommand = 'list';
93
        if (\defined('SIGINT') && SignalRegistry::isSupported()) {
94
            $this->signalRegistry = new SignalRegistry();
95
            $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
96
        }
97
    }
98
99
    /**
100
     * @final
101
     */
102
    public function setDispatcher(EventDispatcherInterface $dispatcher)
103
    {
104
        $this->dispatcher = $dispatcher;
105
    }
106
107
    public function setCommandLoader(CommandLoaderInterface $commandLoader)
108
    {
109
        $this->commandLoader = $commandLoader;
110
    }
111
112
    public function getSignalRegistry(): SignalRegistry
113
    {
114
        if (!$this->signalRegistry) {
115
            throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
116
        }
117
118
        return $this->signalRegistry;
119
    }
120
121
    public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
122
    {
123
        $this->signalsToDispatchEvent = $signalsToDispatchEvent;
124
    }
125
126
    /**
127
     * Runs the current application.
128
     *
129
     * @return int 0 if everything went fine, or an error code
130
     *
131
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
132
     */
133
    public function run(InputInterface $input = null, OutputInterface $output = null)
134
    {
135
        if (\function_exists('putenv')) {
136
            @putenv('LINES='.$this->terminal->getHeight());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for putenv(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

136
            /** @scrutinizer ignore-unhandled */ @putenv('LINES='.$this->terminal->getHeight());

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...
137
            @putenv('COLUMNS='.$this->terminal->getWidth());
138
        }
139
140
        if (null === $input) {
141
            $input = new ArgvInput();
142
        }
143
144
        if (null === $output) {
145
            $output = new ConsoleOutput();
146
        }
147
148
        $renderException = function (\Throwable $e) use ($output) {
149
            if ($output instanceof ConsoleOutputInterface) {
150
                $this->renderThrowable($e, $output->getErrorOutput());
151
            } else {
152
                $this->renderThrowable($e, $output);
153
            }
154
        };
155
        if ($phpHandler = set_exception_handler($renderException)) {
156
            restore_exception_handler();
157
            if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
158
                $errorHandler = true;
159
            } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
160
                $phpHandler[0]->setExceptionHandler($errorHandler);
161
            }
162
        }
163
164
        $this->configureIO($input, $output);
165
166
        try {
167
            $exitCode = $this->doRun($input, $output);
168
        } catch (\Exception $e) {
169
            if (!$this->catchExceptions) {
170
                throw $e;
171
            }
172
173
            $renderException($e);
174
175
            $exitCode = $e->getCode();
176
            if (is_numeric($exitCode)) {
177
                $exitCode = (int) $exitCode;
178
                if (0 === $exitCode) {
179
                    $exitCode = 1;
180
                }
181
            } else {
182
                $exitCode = 1;
183
            }
184
        } finally {
185
            // if the exception handler changed, keep it
186
            // otherwise, unregister $renderException
187
            if (!$phpHandler) {
188
                if (set_exception_handler($renderException) === $renderException) {
189
                    restore_exception_handler();
190
                }
191
                restore_exception_handler();
192
            } elseif (!$errorHandler) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $errorHandler does not seem to be defined for all execution paths leading up to this point.
Loading history...
193
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
194
                if ($finalHandler !== $renderException) {
195
                    $phpHandler[0]->setExceptionHandler($finalHandler);
196
                }
197
            }
198
        }
199
200
        if ($this->autoExit) {
201
            if ($exitCode > 255) {
202
                $exitCode = 255;
203
            }
204
205
            exit($exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
206
        }
207
208
        return $exitCode;
209
    }
210
211
    /**
212
     * Runs the current application.
213
     *
214
     * @return int 0 if everything went fine, or an error code
215
     */
216
    public function doRun(InputInterface $input, OutputInterface $output)
217
    {
218
        if (true === $input->hasParameterOption(['--version', '-V'], true)) {
219
            $output->writeln($this->getLongVersion());
220
221
            return 0;
222
        }
223
224
        try {
225
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
226
            $input->bind($this->getDefinition());
227
        } catch (ExceptionInterface $e) {
228
            // Errors must be ignored, full binding/validation happens later when the command is known.
229
        }
230
231
        $name = $this->getCommandName($input);
232
        if (true === $input->hasParameterOption(['--help', '-h'], true)) {
233
            if (!$name) {
234
                $name = 'help';
235
                $input = new ArrayInput(['command_name' => $this->defaultCommand]);
236
            } else {
237
                $this->wantHelps = true;
238
            }
239
        }
240
241
        if (!$name) {
242
            $name = $this->defaultCommand;
243
            $definition = $this->getDefinition();
244
            $definition->setArguments(array_merge(
245
                $definition->getArguments(),
246
                [
247
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
248
                ]
249
            ));
250
        }
251
252
        try {
253
            $this->runningCommand = null;
254
            // the command name MUST be the first element of the input
255
            $command = $this->find($name);
256
        } catch (\Throwable $e) {
257
            if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
258
                if (null !== $this->dispatcher) {
259
                    $event = new ConsoleErrorEvent($input, $output, $e);
260
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
261
262
                    if (0 === $event->getExitCode()) {
263
                        return 0;
264
                    }
265
266
                    $e = $event->getError();
267
                }
268
269
                throw $e;
270
            }
271
272
            $alternative = $alternatives[0];
273
274
            $style = new SymfonyStyle($input, $output);
275
            $style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
276
            if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
277
                if (null !== $this->dispatcher) {
278
                    $event = new ConsoleErrorEvent($input, $output, $e);
279
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
280
281
                    return $event->getExitCode();
282
                }
283
284
                return 1;
285
            }
286
287
            $command = $this->find($alternative);
288
        }
289
290
        $this->runningCommand = $command;
291
        $exitCode = $this->doRunCommand($command, $input, $output);
292
        $this->runningCommand = null;
293
294
        return $exitCode;
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     */
300
    public function reset()
301
    {
302
    }
303
304
    public function setHelperSet(HelperSet $helperSet)
305
    {
306
        $this->helperSet = $helperSet;
307
    }
308
309
    /**
310
     * Get the helper set associated with the command.
311
     *
312
     * @return HelperSet The HelperSet instance associated with this command
313
     */
314
    public function getHelperSet()
315
    {
316
        if (!$this->helperSet) {
317
            $this->helperSet = $this->getDefaultHelperSet();
318
        }
319
320
        return $this->helperSet;
321
    }
322
323
    public function setDefinition(InputDefinition $definition)
324
    {
325
        $this->definition = $definition;
326
    }
327
328
    /**
329
     * Gets the InputDefinition related to this Application.
330
     *
331
     * @return InputDefinition The InputDefinition instance
332
     */
333
    public function getDefinition()
334
    {
335
        if (!$this->definition) {
336
            $this->definition = $this->getDefaultInputDefinition();
337
        }
338
339
        if ($this->singleCommand) {
340
            $inputDefinition = $this->definition;
341
            $inputDefinition->setArguments();
342
343
            return $inputDefinition;
344
        }
345
346
        return $this->definition;
347
    }
348
349
    /**
350
     * Gets the help message.
351
     *
352
     * @return string A help message
353
     */
354
    public function getHelp()
355
    {
356
        return $this->getLongVersion();
357
    }
358
359
    /**
360
     * Gets whether to catch exceptions or not during commands execution.
361
     *
362
     * @return bool Whether to catch exceptions or not during commands execution
363
     */
364
    public function areExceptionsCaught()
365
    {
366
        return $this->catchExceptions;
367
    }
368
369
    /**
370
     * Sets whether to catch exceptions or not during commands execution.
371
     */
372
    public function setCatchExceptions(bool $boolean)
373
    {
374
        $this->catchExceptions = $boolean;
375
    }
376
377
    /**
378
     * Gets whether to automatically exit after a command execution or not.
379
     *
380
     * @return bool Whether to automatically exit after a command execution or not
381
     */
382
    public function isAutoExitEnabled()
383
    {
384
        return $this->autoExit;
385
    }
386
387
    /**
388
     * Sets whether to automatically exit after a command execution or not.
389
     */
390
    public function setAutoExit(bool $boolean)
391
    {
392
        $this->autoExit = $boolean;
393
    }
394
395
    /**
396
     * Gets the name of the application.
397
     *
398
     * @return string The application name
399
     */
400
    public function getName()
401
    {
402
        return $this->name;
403
    }
404
405
    /**
406
     * Sets the application name.
407
     **/
408
    public function setName(string $name)
409
    {
410
        $this->name = $name;
411
    }
412
413
    /**
414
     * Gets the application version.
415
     *
416
     * @return string The application version
417
     */
418
    public function getVersion()
419
    {
420
        return $this->version;
421
    }
422
423
    /**
424
     * Sets the application version.
425
     */
426
    public function setVersion(string $version)
427
    {
428
        $this->version = $version;
429
    }
430
431
    /**
432
     * Returns the long version of the application.
433
     *
434
     * @return string The long application version
435
     */
436
    public function getLongVersion()
437
    {
438
        if ('UNKNOWN' !== $this->getName()) {
439
            if ('UNKNOWN' !== $this->getVersion()) {
440
                return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
441
            }
442
443
            return $this->getName();
444
        }
445
446
        return 'Console Tool';
447
    }
448
449
    /**
450
     * Registers a new command.
451
     *
452
     * @return Command The newly created command
453
     */
454
    public function register(string $name)
455
    {
456
        return $this->add(new Command($name));
457
    }
458
459
    /**
460
     * Adds an array of command objects.
461
     *
462
     * If a Command is not enabled it will not be added.
463
     *
464
     * @param Command[] $commands An array of commands
465
     */
466
    public function addCommands(array $commands)
467
    {
468
        foreach ($commands as $command) {
469
            $this->add($command);
470
        }
471
    }
472
473
    /**
474
     * Adds a command object.
475
     *
476
     * If a command with the same name already exists, it will be overridden.
477
     * If the command is not enabled it will not be added.
478
     *
479
     * @return Command|null The registered command if enabled or null
480
     */
481
    public function add(Command $command)
482
    {
483
        $this->init();
484
485
        $command->setApplication($this);
486
487
        if (!$command->isEnabled()) {
488
            $command->setApplication(null);
489
490
            return null;
491
        }
492
493
        if (!$command instanceof LazyCommand) {
494
            // Will throw if the command is not correctly initialized.
495
            $command->getDefinition();
496
        }
497
498
        if (!$command->getName()) {
499
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
500
        }
501
502
        $this->commands[$command->getName()] = $command;
503
504
        foreach ($command->getAliases() as $alias) {
505
            $this->commands[$alias] = $command;
506
        }
507
508
        return $command;
509
    }
510
511
    /**
512
     * Returns a registered command by name or alias.
513
     *
514
     * @return Command A Command object
515
     *
516
     * @throws CommandNotFoundException When given command name does not exist
517
     */
518
    public function get(string $name)
519
    {
520
        $this->init();
521
522
        if (!$this->has($name)) {
523
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
524
        }
525
526
        // When the command has a different name than the one used at the command loader level
527
        if (!isset($this->commands[$name])) {
528
            throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
529
        }
530
531
        $command = $this->commands[$name];
532
533
        if ($this->wantHelps) {
534
            $this->wantHelps = false;
535
536
            $helpCommand = $this->get('help');
537
            $helpCommand->setCommand($command);
0 ignored issues
show
Bug introduced by
The method setCommand() does not exist on Symfony\Component\Console\Command\Command. Did you maybe mean setCode()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

537
            $helpCommand->/** @scrutinizer ignore-call */ 
538
                          setCommand($command);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
538
539
            return $helpCommand;
540
        }
541
542
        return $command;
543
    }
544
545
    /**
546
     * Returns true if the command exists, false otherwise.
547
     *
548
     * @return bool true if the command exists, false otherwise
549
     */
550
    public function has(string $name)
551
    {
552
        $this->init();
553
554
        return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
555
    }
556
557
    /**
558
     * Returns an array of all unique namespaces used by currently registered commands.
559
     *
560
     * It does not return the global namespace which always exists.
561
     *
562
     * @return string[] An array of namespaces
563
     */
564
    public function getNamespaces()
565
    {
566
        $namespaces = [];
567
        foreach ($this->all() as $command) {
568
            if ($command->isHidden()) {
569
                continue;
570
            }
571
572
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
0 ignored issues
show
Bug introduced by
It seems like $command->getName() can also be of type null; however, parameter $name of Symfony\Component\Consol...:extractAllNamespaces() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

572
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces(/** @scrutinizer ignore-type */ $command->getName()));
Loading history...
573
574
            foreach ($command->getAliases() as $alias) {
575
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
576
            }
577
        }
578
579
        return array_values(array_unique(array_filter($namespaces)));
580
    }
581
582
    /**
583
     * Finds a registered namespace by a name or an abbreviation.
584
     *
585
     * @return string A registered namespace
586
     *
587
     * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
588
     */
589
    public function findNamespace(string $namespace)
590
    {
591
        $allNamespaces = $this->getNamespaces();
592
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';
593
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
594
595
        if (empty($namespaces)) {
596
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
597
598
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
599
                if (1 == \count($alternatives)) {
600
                    $message .= "\n\nDid you mean this?\n    ";
601
                } else {
602
                    $message .= "\n\nDid you mean one of these?\n    ";
603
                }
604
605
                $message .= implode("\n    ", $alternatives);
606
            }
607
608
            throw new NamespaceNotFoundException($message, $alternatives);
609
        }
610
611
        $exact = \in_array($namespace, $namespaces, true);
612
        if (\count($namespaces) > 1 && !$exact) {
613
            throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
614
        }
615
616
        return $exact ? $namespace : reset($namespaces);
617
    }
618
619
    /**
620
     * Finds a command by name or alias.
621
     *
622
     * Contrary to get, this command tries to find the best
623
     * match if you give it an abbreviation of a name or alias.
624
     *
625
     * @return Command A Command instance
626
     *
627
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
628
     */
629
    public function find(string $name)
630
    {
631
        $this->init();
632
633
        $aliases = [];
634
635
        foreach ($this->commands as $command) {
636
            foreach ($command->getAliases() as $alias) {
637
                if (!$this->has($alias)) {
638
                    $this->commands[$alias] = $command;
639
                }
640
            }
641
        }
642
643
        if ($this->has($name)) {
644
            return $this->get($name);
645
        }
646
647
        $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
648
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*';
649
        $commands = preg_grep('{^'.$expr.'}', $allCommands);
650
651
        if (empty($commands)) {
652
            $commands = preg_grep('{^'.$expr.'}i', $allCommands);
653
        }
654
655
        // if no commands matched or we just matched namespaces
656
        if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
657
            if (false !== $pos = strrpos($name, ':')) {
658
                // check if a namespace exists and contains commands
659
                $this->findNamespace(substr($name, 0, $pos));
660
            }
661
662
            $message = sprintf('Command "%s" is not defined.', $name);
663
664
            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
665
                // remove hidden commands
666
                $alternatives = array_filter($alternatives, function ($name) {
667
                    return !$this->get($name)->isHidden();
668
                });
669
670
                if (1 == \count($alternatives)) {
671
                    $message .= "\n\nDid you mean this?\n    ";
672
                } else {
673
                    $message .= "\n\nDid you mean one of these?\n    ";
674
                }
675
                $message .= implode("\n    ", $alternatives);
676
            }
677
678
            throw new CommandNotFoundException($message, array_values($alternatives));
679
        }
680
681
        // filter out aliases for commands which are already on the list
682
        if (\count($commands) > 1) {
683
            $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
684
            $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
685
                if (!$commandList[$nameOrAlias] instanceof Command) {
686
                    $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
687
                }
688
689
                $commandName = $commandList[$nameOrAlias]->getName();
690
691
                $aliases[$nameOrAlias] = $commandName;
692
693
                return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
694
            }));
695
        }
696
697
        if (\count($commands) > 1) {
698
            $usableWidth = $this->terminal->getWidth() - 10;
699
            $abbrevs = array_values($commands);
700
            $maxLen = 0;
701
            foreach ($abbrevs as $abbrev) {
702
                $maxLen = max(Helper::width($abbrev), $maxLen);
703
            }
704
            $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $commandList does not seem to be defined for all execution paths leading up to this point.
Loading history...
705
                if ($commandList[$cmd]->isHidden()) {
706
                    unset($commands[array_search($cmd, $commands)]);
707
708
                    return false;
709
                }
710
711
                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
712
713
                return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
714
            }, array_values($commands));
715
716
            if (\count($commands) > 1) {
717
                $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
718
719
                throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
720
            }
721
        }
722
723
        $command = $this->get(reset($commands));
724
725
        if ($command->isHidden()) {
726
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
727
        }
728
729
        return $command;
730
    }
731
732
    /**
733
     * Gets the commands (registered in the given namespace if provided).
734
     *
735
     * The array keys are the full names and the values the command instances.
736
     *
737
     * @return Command[] An array of Command instances
738
     */
739
    public function all(string $namespace = null)
740
    {
741
        $this->init();
742
743
        if (null === $namespace) {
744
            if (!$this->commandLoader) {
745
                return $this->commands;
746
            }
747
748
            $commands = $this->commands;
749
            foreach ($this->commandLoader->getNames() as $name) {
750
                if (!isset($commands[$name]) && $this->has($name)) {
751
                    $commands[$name] = $this->get($name);
752
                }
753
            }
754
755
            return $commands;
756
        }
757
758
        $commands = [];
759
        foreach ($this->commands as $name => $command) {
760
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
761
                $commands[$name] = $command;
762
            }
763
        }
764
765
        if ($this->commandLoader) {
766
            foreach ($this->commandLoader->getNames() as $name) {
767
                if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
768
                    $commands[$name] = $this->get($name);
769
                }
770
            }
771
        }
772
773
        return $commands;
774
    }
775
776
    /**
777
     * Returns an array of possible abbreviations given a set of names.
778
     *
779
     * @return string[][] An array of abbreviations
780
     */
781
    public static function getAbbreviations(array $names)
782
    {
783
        $abbrevs = [];
784
        foreach ($names as $name) {
785
            for ($len = \strlen($name); $len > 0; --$len) {
786
                $abbrev = substr($name, 0, $len);
787
                $abbrevs[$abbrev][] = $name;
788
            }
789
        }
790
791
        return $abbrevs;
792
    }
793
794
    public function renderThrowable(\Throwable $e, OutputInterface $output): void
795
    {
796
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);
797
798
        $this->doRenderThrowable($e, $output);
799
800
        if (null !== $this->runningCommand) {
801
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
802
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
803
        }
804
    }
805
806
    protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
807
    {
808
        do {
809
            $message = trim($e->getMessage());
810
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
811
                $class = get_debug_type($e);
812
                $title = sprintf('  [%s%s]  ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
813
                $len = Helper::width($title);
814
            } else {
815
                $len = 0;
816
            }
817
818
            if (false !== strpos($message, "@anonymous\0")) {
819
                $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
820
                    return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
821
                }, $message);
822
            }
823
824
            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
825
            $lines = [];
826
            foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
827
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
0 ignored issues
show
Comprehensibility Bug introduced by
$line is overwriting a variable from outer foreach loop.
Loading history...
828
                    // pre-format lines to get the right string length
829
                    $lineLength = Helper::width($line) + 4;
830
                    $lines[] = [$line, $lineLength];
831
832
                    $len = max($lineLength, $len);
833
                }
834
            }
835
836
            $messages = [];
837
            if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
838
                $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
839
            }
840
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
841
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
842
                $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::width($title))));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $title does not seem to be defined for all execution paths leading up to this point.
Loading history...
843
            }
844
            foreach ($lines as $line) {
845
                $messages[] = sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
846
            }
847
            $messages[] = $emptyLine;
848
            $messages[] = '';
849
850
            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
851
852
            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
853
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
854
855
                // exception related properties
856
                $trace = $e->getTrace();
857
858
                array_unshift($trace, [
859
                    'function' => '',
860
                    'file' => $e->getFile() ?: 'n/a',
861
                    'line' => $e->getLine() ?: 'n/a',
862
                    'args' => [],
863
                ]);
864
865
                for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
866
                    $class = $trace[$i]['class'] ?? '';
867
                    $type = $trace[$i]['type'] ?? '';
868
                    $function = $trace[$i]['function'] ?? '';
869
                    $file = $trace[$i]['file'] ?? 'n/a';
870
                    $line = $trace[$i]['line'] ?? 'n/a';
871
872
                    $output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
873
                }
874
875
                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
876
            }
877
        } while ($e = $e->getPrevious());
878
    }
879
880
    /**
881
     * Configures the input and output instances based on the user arguments and options.
882
     */
883
    protected function configureIO(InputInterface $input, OutputInterface $output)
884
    {
885
        if (true === $input->hasParameterOption(['--ansi'], true)) {
886
            $output->setDecorated(true);
887
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
888
            $output->setDecorated(false);
889
        }
890
891
        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
892
            $input->setInteractive(false);
893
        }
894
895
        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
896
            case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
897
            case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
898
            case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
899
            case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
900
            default: $shellVerbosity = 0; break;
901
        }
902
903
        if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
904
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
905
            $shellVerbosity = -1;
906
        } else {
907
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
908
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
909
                $shellVerbosity = 3;
910
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
911
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
912
                $shellVerbosity = 2;
913
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
914
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
915
                $shellVerbosity = 1;
916
            }
917
        }
918
919
        if (-1 === $shellVerbosity) {
920
            $input->setInteractive(false);
921
        }
922
923
        if (\function_exists('putenv')) {
924
            @putenv('SHELL_VERBOSITY='.$shellVerbosity);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for putenv(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

924
            /** @scrutinizer ignore-unhandled */ @putenv('SHELL_VERBOSITY='.$shellVerbosity);

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...
925
        }
926
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
927
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
928
    }
929
930
    /**
931
     * Runs the current command.
932
     *
933
     * If an event dispatcher has been attached to the application,
934
     * events are also dispatched during the life-cycle of the command.
935
     *
936
     * @return int 0 if everything went fine, or an error code
937
     */
938
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
939
    {
940
        foreach ($command->getHelperSet() as $helper) {
941
            if ($helper instanceof InputAwareInterface) {
942
                $helper->setInput($input);
943
            }
944
        }
945
946
        if ($command instanceof SignalableCommandInterface) {
947
            if (!$this->signalRegistry) {
948
                throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
949
            }
950
951
            if ($this->dispatcher) {
952
                foreach ($this->signalsToDispatchEvent as $signal) {
953
                    $event = new ConsoleSignalEvent($command, $input, $output, $signal);
954
955
                    $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
956
                        $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
957
958
                        // No more handlers, we try to simulate PHP default behavior
959
                        if (!$hasNext) {
960
                            if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
961
                                exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
962
                            }
963
                        }
964
                    });
965
                }
966
            }
967
968
            foreach ($command->getSubscribedSignals() as $signal) {
969
                $this->signalRegistry->register($signal, [$command, 'handleSignal']);
970
            }
971
        }
972
973
        if (null === $this->dispatcher) {
974
            return $command->run($input, $output);
975
        }
976
977
        // bind before the console.command event, so the listeners have access to input options/arguments
978
        try {
979
            $command->mergeApplicationDefinition();
980
            $input->bind($command->getDefinition());
981
        } catch (ExceptionInterface $e) {
982
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
983
        }
984
985
        $event = new ConsoleCommandEvent($command, $input, $output);
986
        $e = null;
987
988
        try {
989
            $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
990
991
            if ($event->commandShouldRun()) {
992
                $exitCode = $command->run($input, $output);
993
            } else {
994
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
995
            }
996
        } catch (\Throwable $e) {
997
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
998
            $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
999
            $e = $event->getError();
1000
1001
            if (0 === $exitCode = $event->getExitCode()) {
1002
                $e = null;
1003
            }
1004
        }
1005
1006
        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
1007
        $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
1008
1009
        if (null !== $e) {
1010
            throw $e;
1011
        }
1012
1013
        return $event->getExitCode();
1014
    }
1015
1016
    /**
1017
     * Gets the name of the command based on input.
1018
     *
1019
     * @return string|null
1020
     */
1021
    protected function getCommandName(InputInterface $input)
1022
    {
1023
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
1024
    }
1025
1026
    /**
1027
     * Gets the default input definition.
1028
     *
1029
     * @return InputDefinition An InputDefinition instance
1030
     */
1031
    protected function getDefaultInputDefinition()
1032
    {
1033
        return new InputDefinition([
1034
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
1035
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),
1036
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
1037
            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'),
1038
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
1039
            new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', false),
1040
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
1041
        ]);
1042
    }
1043
1044
    /**
1045
     * Gets the default commands that should always be available.
1046
     *
1047
     * @return Command[] An array of default Command instances
1048
     */
1049
    protected function getDefaultCommands()
1050
    {
1051
        return [new HelpCommand(), new ListCommand()];
1052
    }
1053
1054
    /**
1055
     * Gets the default helper set with the helpers that should always be available.
1056
     *
1057
     * @return HelperSet A HelperSet instance
1058
     */
1059
    protected function getDefaultHelperSet()
1060
    {
1061
        return new HelperSet([
1062
            new FormatterHelper(),
1063
            new DebugFormatterHelper(),
1064
            new ProcessHelper(),
1065
            new QuestionHelper(),
1066
        ]);
1067
    }
1068
1069
    /**
1070
     * Returns abbreviated suggestions in string format.
1071
     */
1072
    private function getAbbreviationSuggestions(array $abbrevs): string
1073
    {
1074
        return '    '.implode("\n    ", $abbrevs);
1075
    }
1076
1077
    /**
1078
     * Returns the namespace part of the command name.
1079
     *
1080
     * This method is not part of public API and should not be used directly.
1081
     *
1082
     * @return string The namespace of the command
1083
     */
1084
    public function extractNamespace(string $name, int $limit = null)
1085
    {
1086
        $parts = explode(':', $name, -1);
1087
1088
        return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
1089
    }
1090
1091
    /**
1092
     * Finds alternative of $name among $collection,
1093
     * if nothing is found in $collection, try in $abbrevs.
1094
     *
1095
     * @return string[] A sorted array of similar string
1096
     */
1097
    private function findAlternatives(string $name, iterable $collection): array
1098
    {
1099
        $threshold = 1e3;
1100
        $alternatives = [];
1101
1102
        $collectionParts = [];
1103
        foreach ($collection as $item) {
1104
            $collectionParts[$item] = explode(':', $item);
1105
        }
1106
1107
        foreach (explode(':', $name) as $i => $subname) {
1108
            foreach ($collectionParts as $collectionName => $parts) {
1109
                $exists = isset($alternatives[$collectionName]);
1110
                if (!isset($parts[$i]) && $exists) {
1111
                    $alternatives[$collectionName] += $threshold;
1112
                    continue;
1113
                } elseif (!isset($parts[$i])) {
1114
                    continue;
1115
                }
1116
1117
                $lev = levenshtein($subname, $parts[$i]);
1118
                if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
1119
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
1120
                } elseif ($exists) {
1121
                    $alternatives[$collectionName] += $threshold;
1122
                }
1123
            }
1124
        }
1125
1126
        foreach ($collection as $item) {
1127
            $lev = levenshtein($name, $item);
1128
            if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
1129
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
1130
            }
1131
        }
1132
1133
        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
1134
        ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
1135
1136
        return array_keys($alternatives);
1137
    }
1138
1139
    /**
1140
     * Sets the default Command name.
1141
     *
1142
     * @return self
1143
     */
1144
    public function setDefaultCommand(string $commandName, bool $isSingleCommand = false)
1145
    {
1146
        $this->defaultCommand = $commandName;
1147
1148
        if ($isSingleCommand) {
1149
            // Ensure the command exist
1150
            $this->find($commandName);
1151
1152
            $this->singleCommand = true;
1153
        }
1154
1155
        return $this;
1156
    }
1157
1158
    /**
1159
     * @internal
1160
     */
1161
    public function isSingleCommand(): bool
1162
    {
1163
        return $this->singleCommand;
1164
    }
1165
1166
    private function splitStringByWidth(string $string, int $width): array
1167
    {
1168
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1169
        // additionally, array_slice() is not enough as some character has doubled width.
1170
        // we need a function to split string not by character count but by string width
1171
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
1172
            return str_split($string, $width);
0 ignored issues
show
Bug Best Practice introduced by
The expression return str_split($string, $width) could return the type true which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
1173
        }
1174
1175
        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1176
        $lines = [];
1177
        $line = '';
1178
1179
        $offset = 0;
1180
        while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
0 ignored issues
show
Bug introduced by
It seems like $utf8String can also be of type array; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1180
        while (preg_match('/.{1,10000}/u', /** @scrutinizer ignore-type */ $utf8String, $m, 0, $offset)) {
Loading history...
1181
            $offset += \strlen($m[0]);
1182
1183
            foreach (preg_split('//u', $m[0]) as $char) {
1184
                // test if $char could be appended to current line
1185
                if (mb_strwidth($line.$char, 'utf8') <= $width) {
1186
                    $line .= $char;
1187
                    continue;
1188
                }
1189
                // if not, push current line to array and make new line
1190
                $lines[] = str_pad($line, $width);
1191
                $line = $char;
1192
            }
1193
        }
1194
1195
        $lines[] = \count($lines) ? str_pad($line, $width) : $line;
1196
1197
        mb_convert_variables($encoding, 'utf8', $lines);
1198
1199
        return $lines;
1200
    }
1201
1202
    /**
1203
     * Returns all namespaces of the command name.
1204
     *
1205
     * @return string[] The namespaces of the command
1206
     */
1207
    private function extractAllNamespaces(string $name): array
1208
    {
1209
        // -1 as third argument is needed to skip the command short name when exploding
1210
        $parts = explode(':', $name, -1);
1211
        $namespaces = [];
1212
1213
        foreach ($parts as $part) {
1214
            if (\count($namespaces)) {
1215
                $namespaces[] = end($namespaces).':'.$part;
1216
            } else {
1217
                $namespaces[] = $part;
1218
            }
1219
        }
1220
1221
        return $namespaces;
1222
    }
1223
1224
    private function init()
1225
    {
1226
        if ($this->initialized) {
1227
            return;
1228
        }
1229
        $this->initialized = true;
1230
1231
        foreach ($this->getDefaultCommands() as $command) {
1232
            $this->add($command);
1233
        }
1234
    }
1235
}
1236