Completed
Push — 6.0 ( 3e2b0c...1081e9 )
by yun
02:23
created

Console::makeRequest()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 23
nc 24
nop 0
dl 0
loc 41
ccs 0
cts 23
cp 0
crap 42
rs 8.9297
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
3
// | TopThink [ WE CAN DO IT JUST THINK IT ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Author: zhangyajun <[email protected]>
8
// +----------------------------------------------------------------------
9
declare (strict_types = 1);
10
11
namespace think;
12
13
use Closure;
14
use InvalidArgumentException;
15
use LogicException;
16
use think\console\Command;
17
use think\console\command\Clear;
18
use think\console\command\Help;
19
use think\console\command\Help as HelpCommand;
20
use think\console\command\Lists;
21
use think\console\command\make\Command as MakeCommand;
22
use think\console\command\make\Controller;
23
use think\console\command\make\Event;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Event. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
24
use think\console\command\make\Listener;
25
use think\console\command\make\Middleware;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Middleware. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
26
use think\console\command\make\Model;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Model. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
27
use think\console\command\make\Service;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Service. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
28
use think\console\command\make\Subscribe;
29
use think\console\command\make\Validate;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Validate. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
30
use think\console\command\optimize\Route;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Route. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
31
use think\console\command\optimize\Schema;
32
use think\console\command\RouteList;
33
use think\console\command\RunServer;
34
use think\console\command\ServiceDiscover;
35
use think\console\command\VendorPublish;
36
use think\console\command\Version;
37
use think\console\Input;
38
use think\console\input\Argument as InputArgument;
39
use think\console\input\Definition as InputDefinition;
40
use think\console\input\Option as InputOption;
41
use think\console\Output;
42
use think\console\output\driver\Buffer;
43
44
/**
45
 * 控制台应用管理类
46
 */
47
class Console
48
{
49
50
    protected $app;
51
52
    /** @var Command[] */
53
    protected $commands = [];
54
55
    protected $wantHelps = false;
56
57
    protected $catchExceptions = true;
58
    protected $autoExit        = true;
59
    protected $definition;
60
    protected $defaultCommand  = 'list';
61
62
    protected $defaultCommands = [
63
        'help'             => Help::class,
64
        'list'             => Lists::class,
65
        'clear'            => Clear::class,
66
        'make:command'     => MakeCommand::class,
67
        'make:controller'  => Controller::class,
68
        'make:model'       => Model::class,
69
        'make:middleware'  => Middleware::class,
70
        'make:validate'    => Validate::class,
71
        'make:event'       => Event::class,
72
        'make:listener'    => Listener::class,
73
        'make:service'     => Service::class,
74
        'make:subscribe'   => Subscribe::class,
75
        'optimize:route'   => Route::class,
76
        'optimize:schema'  => Schema::class,
77
        'run'              => RunServer::class,
78
        'version'          => Version::class,
79
        'route:list'       => RouteList::class,
80
        'service:discover' => ServiceDiscover::class,
81
        'vendor:publish'   => VendorPublish::class,
82
    ];
83
84
    /**
85
     * 启动器
86
     * @var array
87
     */
88
    protected static $startCallbacks = [];
89
90
    public function __construct(App $app)
91
    {
92
        $this->app = $app;
93
94
        $this->initialize();
95
96
        $this->definition = $this->getDefaultInputDefinition();
97
98
        //加载指令
99
        $this->loadCommands();
100
101
        $this->start();
102
    }
103
104
    /**
105
     * 初始化
106
     */
107
    protected function initialize()
108
    {
109
        if (!$this->app->initialized()) {
110
            $this->app->initialize();
111
        }
112
        $this->makeRequest();
113
    }
114
115
    /**
116
     * 构造request
117
     */
118
    protected function makeRequest()
119
    {
120
        $uri = $this->app->config->get('app.url', 'http://localhost');
121
122
        $components = parse_url($uri);
0 ignored issues
show
Bug introduced by
It seems like $uri can also be of type array; however, parameter $url of parse_url() 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

122
        $components = parse_url(/** @scrutinizer ignore-type */ $uri);
Loading history...
123
124
        $server = $_SERVER;
125
126
        if (isset($components['path'])) {
127
            $server = array_merge($server, [
128
                'SCRIPT_FILENAME' => $components['path'],
129
                'SCRIPT_NAME'     => $components['path'],
130
            ]);
131
        }
132
133
        if (isset($components['host'])) {
134
            $server['SERVER_NAME'] = $components['host'];
135
            $server['HTTP_HOST']   = $components['host'];
136
        }
137
138
        if (isset($components['scheme'])) {
139
            if ('https' === $components['scheme']) {
140
                $server['HTTPS']       = 'on';
141
                $server['SERVER_PORT'] = 443;
142
            } else {
143
                unset($server['HTTPS']);
144
                $server['SERVER_PORT'] = 80;
145
            }
146
        }
147
148
        if (isset($components['port'])) {
149
            $server['SERVER_PORT'] = $components['port'];
150
            $server['HTTP_HOST']   .= ':' . $components['port'];
151
        }
152
153
        $server['REQUEST_URI'] = $uri;
154
155
        /** @var Request $request */
156
        $request = $this->app->make('request', [], true);
157
158
        $request->withServer($server);
159
    }
160
161
    /**
162
     * 添加初始化器
163
     * @param Closure $callback
164
     */
165
    public static function starting(Closure $callback): void
166
    {
167
        static::$startCallbacks[] = $callback;
168
    }
169
170
    /**
171
     * 清空启动器
172
     */
173
    public static function flushStartCallbacks(): void
174
    {
175
        static::$startCallbacks = [];
176
    }
177
178
    /**
179
     * 设置执行用户
180
     * @param $user
181
     */
182
    public static function setUser(string $user): void
183
    {
184
        if (extension_loaded('posix')) {
185
            $user = posix_getpwnam($user);
186
187
            if (!empty($user)) {
188
                posix_setgid($user['gid']);
189
                posix_setuid($user['uid']);
190
            }
191
        }
192
    }
193
194
    /**
195
     * 启动
196
     */
197
    protected function start(): void
198
    {
199
        foreach (static::$startCallbacks as $callback) {
200
            $callback($this);
201
        }
202
    }
203
204
    /**
205
     * 加载指令
206
     * @access protected
207
     */
208
    protected function loadCommands(): void
209
    {
210
        $commands = $this->app->config->get('console.commands', []);
211
        $commands = array_merge($this->defaultCommands, $commands);
212
213
        $this->addCommands($commands);
214
    }
215
216
    /**
217
     * @access public
218
     * @param string $command
219
     * @param array $parameters
220
     * @param string $driver
221
     * @return Output|Buffer
222
     */
223
    public function call(string $command, array $parameters = [], string $driver = 'buffer')
224
    {
225
        array_unshift($parameters, $command);
226
227
        $input  = new Input($parameters);
228
        $output = new Output($driver);
229
230
        $this->setCatchExceptions(false);
231
        $this->find($command)->run($input, $output);
232
233
        return $output;
234
    }
235
236
    /**
237
     * 执行当前的指令
238
     * @access public
239
     * @return int
240
     * @throws \Exception
241
     * @api
242
     */
243
    public function run()
244
    {
245
        $input  = new Input();
246
        $output = new Output();
247
248
        $this->configureIO($input, $output);
249
250
        try {
251
            $exitCode = $this->doRun($input, $output);
252
        } catch (\Exception $e) {
253
            if (!$this->catchExceptions) {
254
                throw $e;
255
            }
256
257
            $output->renderException($e);
258
259
            $exitCode = $e->getCode();
260
            if (is_numeric($exitCode)) {
261
                $exitCode = (int) $exitCode;
262
                if (0 === $exitCode) {
263
                    $exitCode = 1;
264
                }
265
            } else {
266
                $exitCode = 1;
267
            }
268
        }
269
270
        if ($this->autoExit) {
271
            if ($exitCode > 255) {
272
                $exitCode = 255;
273
            }
274
275
            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...
276
        }
277
278
        return $exitCode;
279
    }
280
281
    /**
282
     * 执行指令
283
     * @access public
284
     * @param Input $input
285
     * @param Output $output
286
     * @return int
287
     */
288
    public function doRun(Input $input, Output $output)
289
    {
290
        if (true === $input->hasParameterOption(['--version', '-V'])) {
291
            $output->writeln($this->getLongVersion());
292
293
            return 0;
294
        }
295
296
        $name = $this->getCommandName($input);
297
298
        if (true === $input->hasParameterOption(['--help', '-h'])) {
299
            if (!$name) {
300
                $name  = 'help';
301
                $input = new Input(['help']);
302
            } else {
303
                $this->wantHelps = true;
304
            }
305
        }
306
307
        if (!$name) {
308
            $name  = $this->defaultCommand;
309
            $input = new Input([$this->defaultCommand]);
310
        }
311
312
        $command = $this->find($name);
313
314
        return $this->doRunCommand($command, $input, $output);
315
    }
316
317
    /**
318
     * 设置输入参数定义
319
     * @access public
320
     * @param InputDefinition $definition
321
     */
322
    public function setDefinition(InputDefinition $definition): void
323
    {
324
        $this->definition = $definition;
325
    }
326
327
    /**
328
     * 获取输入参数定义
329
     * @access public
330
     * @return InputDefinition The InputDefinition instance
331
     */
332
    public function getDefinition(): InputDefinition
333
    {
334
        return $this->definition;
335
    }
336
337
    /**
338
     * Gets the help message.
339
     * @access public
340
     * @return string A help message.
341
     */
342
    public function getHelp(): string
343
    {
344
        return $this->getLongVersion();
345
    }
346
347
    /**
348
     * 是否捕获异常
349
     * @access public
350
     * @param bool $boolean
351
     * @api
352
     */
353
    public function setCatchExceptions(bool $boolean): void
354
    {
355
        $this->catchExceptions = $boolean;
356
    }
357
358
    /**
359
     * 是否自动退出
360
     * @access public
361
     * @param bool $boolean
362
     * @api
363
     */
364
    public function setAutoExit(bool $boolean): void
365
    {
366
        $this->autoExit = $boolean;
367
    }
368
369
    /**
370
     * 获取完整的版本号
371
     * @access public
372
     * @return string
373
     */
374
    public function getLongVersion(): string
375
    {
376
        if ($this->app->version()) {
377
            return sprintf('version <comment>%s</comment>', $this->app->version());
378
        }
379
380
        return '<info>Console Tool</info>';
381
    }
382
383
    /**
384
     * 添加指令集
385
     * @access public
386
     * @param array $commands
387
     */
388
    public function addCommands(array $commands): void
389
    {
390
        foreach ($commands as $key => $command) {
391
            if (is_subclass_of($command, Command::class)) {
392
                // 注册指令
393
                $this->addCommand($command, is_numeric($key) ? '' : $key);
394
            }
395
        }
396
    }
397
398
    /**
399
     * 添加一个指令
400
     * @access public
401
     * @param string|Command $command 指令对象或者指令类名
402
     * @param string $name 指令名 留空则自动获取
403
     * @return Command|void
404
     */
405
    public function addCommand($command, string $name = '')
406
    {
407
        if ($name) {
408
            $this->commands[$name] = $command;
409
            return;
410
        }
411
412
        if (is_string($command)) {
413
            $command = $this->app->invokeClass($command);
414
        }
415
416
        $command->setConsole($this);
417
418
        if (!$command->isEnabled()) {
419
            $command->setConsole(null);
420
            return;
421
        }
422
423
        $command->setApp($this->app);
424
425
        if (null === $command->getDefinition()) {
426
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
427
        }
428
429
        $this->commands[$command->getName()] = $command;
430
431
        foreach ($command->getAliases() as $alias) {
432
            $this->commands[$alias] = $command;
433
        }
434
435
        return $command;
436
    }
437
438
    /**
439
     * 获取指令
440
     * @access public
441
     * @param string $name 指令名称
442
     * @return Command
443
     * @throws InvalidArgumentException
444
     */
445
    public function getCommand(string $name): Command
446
    {
447
        if (!isset($this->commands[$name])) {
448
            throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
449
        }
450
451
        $command = $this->commands[$name];
452
453
        if (is_string($command)) {
0 ignored issues
show
introduced by
The condition is_string($command) is always false.
Loading history...
454
            $command = $this->app->invokeClass($command);
455
            /** @var Command $command */
456
            $command->setConsole($this);
457
            $command->setApp($this->app);
458
        }
459
460
        if ($this->wantHelps) {
461
            $this->wantHelps = false;
462
463
            /** @var HelpCommand $helpCommand */
464
            $helpCommand = $this->getCommand('help');
465
            $helpCommand->setCommand($command);
466
467
            return $helpCommand;
468
        }
469
470
        return $command;
471
    }
472
473
    /**
474
     * 某个指令是否存在
475
     * @access public
476
     * @param string $name 指令名称
477
     * @return bool
478
     */
479
    public function hasCommand(string $name): bool
480
    {
481
        return isset($this->commands[$name]);
482
    }
483
484
    /**
485
     * 获取所有的命名空间
486
     * @access public
487
     * @return array
488
     */
489
    public function getNamespaces(): array
490
    {
491
        $namespaces = [];
492
        foreach ($this->commands as $key => $command) {
493
            if (is_string($command)) {
494
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key));
495
            } else {
496
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
497
498
                foreach ($command->getAliases() as $alias) {
499
                    $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
500
                }
501
            }
502
        }
503
504
        return array_values(array_unique(array_filter($namespaces)));
505
    }
506
507
    /**
508
     * 查找注册命名空间中的名称或缩写。
509
     * @access public
510
     * @param string $namespace
511
     * @return string
512
     * @throws InvalidArgumentException
513
     */
514
    public function findNamespace(string $namespace): string
515
    {
516
        $allNamespaces = $this->getNamespaces();
517
        $expr          = preg_replace_callback('{([^:]+|)}', function ($matches) {
518
            return preg_quote($matches[1]) . '[^:]*';
519
        }, $namespace);
520
        $namespaces    = preg_grep('{^' . $expr . '}', $allNamespaces);
521
522
        if (empty($namespaces)) {
523
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
524
525
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
526
                if (1 == count($alternatives)) {
527
                    $message .= "\n\nDid you mean this?\n    ";
528
                } else {
529
                    $message .= "\n\nDid you mean one of these?\n    ";
530
                }
531
532
                $message .= implode("\n    ", $alternatives);
533
            }
534
535
            throw new InvalidArgumentException($message);
536
        }
537
538
        $exact = in_array($namespace, $namespaces, true);
539
        if (count($namespaces) > 1 && !$exact) {
540
            throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
541
        }
542
543
        return $exact ? $namespace : reset($namespaces);
544
    }
545
546
    /**
547
     * 查找指令
548
     * @access public
549
     * @param string $name 名称或者别名
550
     * @return Command
551
     * @throws InvalidArgumentException
552
     */
553
    public function find(string $name): Command
554
    {
555
        $allCommands = array_keys($this->commands);
556
557
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
558
            return preg_quote($matches[1]) . '[^:]*';
559
        }, $name);
560
561
        $commands = preg_grep('{^' . $expr . '}', $allCommands);
562
563
        if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
564
            if (false !== $pos = strrpos($name, ':')) {
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 InvalidArgumentException($message);
580
        }
581
582
        $exact = in_array($name, $commands, true);
583
        if (count($commands) > 1 && !$exact) {
584
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
585
586
            throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
587
        }
588
589
        return $this->getCommand($exact ? $name : reset($commands));
590
    }
591
592
    /**
593
     * 获取所有的指令
594
     * @access public
595
     * @param string $namespace 命名空间
596
     * @return Command[]
597
     * @api
598
     */
599
    public function all(string $namespace = null): array
600
    {
601
        if (null === $namespace) {
602
            return $this->commands;
603
        }
604
605
        $commands = [];
606
        foreach ($this->commands as $name => $command) {
607
            if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
608
                $commands[$name] = $command;
609
            }
610
        }
611
612
        return $commands;
613
    }
614
615
    /**
616
     * 配置基于用户的参数和选项的输入和输出实例。
617
     * @access protected
618
     * @param Input $input 输入实例
619
     * @param Output $output 输出实例
620
     */
621
    protected function configureIO(Input $input, Output $output): void
622
    {
623
        if (true === $input->hasParameterOption(['--ansi'])) {
624
            $output->setDecorated(true);
625
        } else if (true === $input->hasParameterOption(['--no-ansi'])) {
626
            $output->setDecorated(false);
627
        }
628
629
        if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
630
            $input->setInteractive(false);
631
        }
632
633
        if (true === $input->hasParameterOption(['--quiet', '-q'])) {
634
            $output->setVerbosity(Output::VERBOSITY_QUIET);
635
        } else if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
636
            $output->setVerbosity(Output::VERBOSITY_DEBUG);
637
        } else if ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
638
            $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
639
        } else if ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
640
            $output->setVerbosity(Output::VERBOSITY_VERBOSE);
641
        }
642
    }
643
644
    /**
645
     * 执行指令
646
     * @access protected
647
     * @param Command $command 指令实例
648
     * @param Input $input 输入实例
649
     * @param Output $output 输出实例
650
     * @return int
651
     * @throws \Exception
652
     */
653
    protected function doRunCommand(Command $command, Input $input, Output $output)
654
    {
655
        return $command->run($input, $output);
656
    }
657
658
    /**
659
     * 获取指令的基础名称
660
     * @access protected
661
     * @param Input $input
662
     * @return string
663
     */
664
    protected function getCommandName(Input $input): string
665
    {
666
        return $input->getFirstArgument() ?: '';
667
    }
668
669
    /**
670
     * 获取默认输入定义
671
     * @access protected
672
     * @return InputDefinition
673
     */
674
    protected function getDefaultInputDefinition(): InputDefinition
675
    {
676
        return new InputDefinition([
677
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
678
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
679
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
680
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
681
            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'),
682
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
683
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
684
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
685
        ]);
686
    }
687
688
    /**
689
     * 获取可能的建议
690
     * @access private
691
     * @param array $abbrevs
692
     * @return string
693
     */
694
    private function getAbbreviationSuggestions(array $abbrevs): string
695
    {
696
        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
697
    }
698
699
    /**
700
     * 返回命名空间部分
701
     * @access public
702
     * @param string $name 指令
703
     * @param int $limit 部分的命名空间的最大数量
704
     * @return string
705
     */
706
    public function extractNamespace(string $name, int $limit = 0): string
707
    {
708
        $parts = explode(':', $name);
709
        array_pop($parts);
710
711
        return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
712
    }
713
714
    /**
715
     * 查找可替代的建议
716
     * @access private
717
     * @param string $name
718
     * @param array|\Traversable $collection
719
     * @return array
720
     */
721
    private function findAlternatives(string $name, $collection): array
722
    {
723
        $threshold    = 1e3;
724
        $alternatives = [];
725
726
        $collectionParts = [];
727
        foreach ($collection as $item) {
728
            $collectionParts[$item] = explode(':', $item);
729
        }
730
731
        foreach (explode(':', $name) as $i => $subname) {
732
            foreach ($collectionParts as $collectionName => $parts) {
733
                $exists = isset($alternatives[$collectionName]);
734
                if (!isset($parts[$i]) && $exists) {
735
                    $alternatives[$collectionName] += $threshold;
736
                    continue;
737
                } else if (!isset($parts[$i])) {
738
                    continue;
739
                }
740
741
                $lev = levenshtein($subname, $parts[$i]);
742
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
743
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
744
                } else if ($exists) {
745
                    $alternatives[$collectionName] += $threshold;
746
                }
747
            }
748
        }
749
750
        foreach ($collection as $item) {
751
            $lev = levenshtein($name, $item);
752
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
753
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
754
            }
755
        }
756
757
        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
758
            return $lev < 2 * $threshold;
759
        });
760
        asort($alternatives);
761
762
        return array_keys($alternatives);
763
    }
764
765
    /**
766
     * 返回所有的命名空间
767
     * @access private
768
     * @param string $name
769
     * @return array
770
     */
771
    private function extractAllNamespaces(string $name): array
772
    {
773
        $parts      = explode(':', $name, -1);
774
        $namespaces = [];
775
776
        foreach ($parts as $part) {
777
            if (count($namespaces)) {
778
                $namespaces[] = end($namespaces) . ':' . $part;
779
            } else {
780
                $namespaces[] = $part;
781
            }
782
        }
783
784
        return $namespaces;
785
    }
786
787
}
788