Completed
Push — 6.0 ( 800870...267f59 )
by yun
03:41 queued 17s
created

Console::loadCommands()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
// +----------------------------------------------------------------------
1 ignored issue
show
Coding Style introduced by
You must use "/**" style comments for a file comment
Loading history...
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\Build;
18
use think\console\command\Clear;
19
use think\console\command\Help;
20
use think\console\command\Help as HelpCommand;
21
use think\console\command\Lists;
22
use think\console\command\make\Command as MakeCommand;
23
use think\console\command\make\Controller;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Controller. 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\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...
25
use think\console\command\make\Listener;
26
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...
27
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...
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\Config;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Config. 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\Facade;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Facade. 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...
32
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...
33
use think\console\command\optimize\Schema;
34
use think\console\command\RouteList;
35
use think\console\command\RunServer;
36
use think\console\command\ServiceDiscover;
37
use think\console\command\VendorPublish;
38
use think\console\command\Version;
39
use think\console\Input;
40
use think\console\input\Argument as InputArgument;
41
use think\console\input\Definition as InputDefinition;
42
use think\console\input\Option as InputOption;
43
use think\console\Output;
44
use think\console\output\driver\Buffer;
45
46
/**
47
 * 控制台应用管理类
48
 */
5 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
49
class Console
50
{
51
52
    protected $app;
53
54
    /** @var Command[] */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
55
    protected $commands = [];
56
57
    protected $wantHelps = false;
58
59
    protected $catchExceptions = true;
60
    protected $autoExit        = true;
61
    protected $definition;
62
    protected $defaultCommand  = 'list';
63
64
    protected $defaultCommands = [
65
        'help'             => Help::class,
66
        'list'             => Lists::class,
67
        'build'            => Build::class,
68
        'clear'            => Clear::class,
69
        'make:command'     => MakeCommand::class,
70
        'make:controller'  => Controller::class,
71
        'make:model'       => Model::class,
72
        'make:middleware'  => Middleware::class,
73
        'make:validate'    => Validate::class,
74
        'make:event'       => Event::class,
75
        'make:listener'    => Listener::class,
76
        'make:subscribe'   => Subscribe::class,
77
        'optimize:config'  => Config::class,
78
        'optimize:schema'  => Schema::class,
79
        'optimize:route'   => Route::class,
80
        'optimize:facade'  => Facade::class,
81
        'run'              => RunServer::class,
82
        'version'          => Version::class,
83
        'route:list'       => RouteList::class,
84
        'service:discover' => ServiceDiscover::class,
85
        'vendor:publish'   => VendorPublish::class,
86
    ];
87
88
    /**
89
     * 启动器
90
     * @var array
91
     */
92
    protected static $startCallbacks = [];
93
94
    public function __construct(App $app)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
95
    {
96
        $this->app = $app;
97
98
        $this->app->initialize();
99
100
        $user = $this->app->config->get('console.user');
101
102
        if ($user) {
103
            $this->setUser($user);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type array; however, parameter $user of think\Console::setUser() 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

103
            $this->setUser(/** @scrutinizer ignore-type */ $user);
Loading history...
104
        }
105
106
        $this->definition = $this->getDefaultInputDefinition();
107
108
        //加载指令
109
        $this->loadCommands();
110
111
        $this->start();
112
    }
113
114
    /**
115
     * 添加初始化器
116
     * @param Closure $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
117
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
118
    public static function starting(Closure $callback): void
119
    {
120
        static::$startCallbacks[] = $callback;
121
    }
122
123
    /**
124
     * 清空启动器
125
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
126
    public static function flushStartCallbacks(): void
127
    {
128
        static::$startCallbacks = [];
129
    }
130
131
    /**
132
     * 启动
133
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
134
    protected function start(): void
135
    {
136
        foreach (static::$startCallbacks as $callback) {
137
            $callback($this);
138
        }
139
    }
140
141
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $user should have a doc-comment as per coding-style.
Loading history...
142
     * 设置执行用户
143
     * @param $user
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
144
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
145
    protected function setUser(string $user): void
146
    {
147
        if (extension_loaded('posix')) {
148
            $user = posix_getpwnam($user);
149
150
            if (!empty($user)) {
151
                posix_setuid($user['uid']);
152
                posix_setgid($user['gid']);
153
            }
154
        }
155
    }
156
157
    /**
158
     * 加载指令
159
     * @access protected
160
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
161
    protected function loadCommands(): void
162
    {
163
        $commands = $this->app->config->get('console.commands', []);
164
        $commands = array_merge($this->defaultCommands, $commands);
165
166
        $this->addCommands($commands);
167
    }
168
169
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
170
     * @access public
171
     * @param string $command
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
172
     * @param array  $parameters
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
173
     * @param string $driver
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
174
     * @return Output|Buffer
175
     */
176
    public function call(string $command, array $parameters = [], string $driver = 'buffer')
177
    {
178
        array_unshift($parameters, $command);
179
180
        $input  = new Input($parameters);
181
        $output = new Output($driver);
182
183
        $this->setCatchExceptions(false);
184
        $this->find($command)->run($input, $output);
185
186
        return $output;
187
    }
188
189
    /**
190
     * 执行当前的指令
191
     * @access public
192
     * @return int
193
     * @throws \Exception
194
     * @api
195
     */
196
    public function run()
197
    {
198
        $input  = new Input();
199
        $output = new Output();
200
201
        $this->configureIO($input, $output);
202
203
        try {
204
            $exitCode = $this->doRun($input, $output);
205
        } catch (\Exception $e) {
206
            if (!$this->catchExceptions) {
207
                throw $e;
208
            }
209
210
            $output->renderException($e);
211
212
            $exitCode = $e->getCode();
213
            if (is_numeric($exitCode)) {
214
                $exitCode = (int) $exitCode;
215
                if (0 === $exitCode) {
216
                    $exitCode = 1;
217
                }
218
            } else {
219
                $exitCode = 1;
220
            }
221
        }
222
223
        if ($this->autoExit) {
224
            if ($exitCode > 255) {
225
                $exitCode = 255;
226
            }
227
228
            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...
229
        }
230
231
        return $exitCode;
232
    }
233
234
    /**
235
     * 执行指令
236
     * @access public
237
     * @param Input  $input
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
238
     * @param Output $output
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
239
     * @return int
240
     */
241
    public function doRun(Input $input, Output $output)
242
    {
243
        if (true === $input->hasParameterOption(['--version', '-V'])) {
244
            $output->writeln($this->getLongVersion());
245
246
            return 0;
247
        }
248
249
        $name = $this->getCommandName($input);
250
251
        if (true === $input->hasParameterOption(['--help', '-h'])) {
252
            if (!$name) {
253
                $name  = 'help';
254
                $input = new Input(['help']);
255
            } else {
256
                $this->wantHelps = true;
257
            }
258
        }
259
260
        if (!$name) {
261
            $name  = $this->defaultCommand;
262
            $input = new Input([$this->defaultCommand]);
263
        }
264
265
        $command = $this->find($name);
266
267
        return $this->doRunCommand($command, $input, $output);
268
    }
269
270
    /**
271
     * 设置输入参数定义
272
     * @access public
273
     * @param InputDefinition $definition
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
274
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
275
    public function setDefinition(InputDefinition $definition): void
276
    {
277
        $this->definition = $definition;
278
    }
279
280
    /**
281
     * 获取输入参数定义
282
     * @access public
283
     * @return InputDefinition The InputDefinition instance
284
     */
285
    public function getDefinition(): InputDefinition
286
    {
287
        return $this->definition;
288
    }
289
290
    /**
291
     * Gets the help message.
292
     * @access public
293
     * @return string A help message.
294
     */
295
    public function getHelp(): string
296
    {
297
        return $this->getLongVersion();
298
    }
299
300
    /**
301
     * 是否捕获异常
302
     * @access public
303
     * @param bool $boolean
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
304
     * @api
305
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
306
    public function setCatchExceptions(bool $boolean): void
307
    {
308
        $this->catchExceptions = $boolean;
309
    }
310
311
    /**
312
     * 是否自动退出
313
     * @access public
314
     * @param bool $boolean
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
315
     * @api
316
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
317
    public function setAutoExit(bool $boolean): void
318
    {
319
        $this->autoExit = $boolean;
320
    }
321
322
    /**
323
     * 获取完整的版本号
324
     * @access public
325
     * @return string
326
     */
327
    public function getLongVersion(): string
328
    {
329
        if ($this->app->version()) {
330
            return sprintf('version <comment>%s</comment>', $this->app->version());
331
        }
332
333
        return '<info>Console Tool</info>';
334
    }
335
336
    /**
337
     * 添加指令集
338
     * @access public
339
     * @param array $commands
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
340
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
341
    public function addCommands(array $commands): void
342
    {
343
        foreach ($commands as $key => $command) {
344
            if (is_subclass_of($command, Command::class)) {
345
                // 注册指令
346
                $this->addCommand($command, is_numeric($key) ? '' : $key);
347
            }
348
        }
349
    }
350
351
    /**
352
     * 添加一个指令
353
     * @access public
354
     * @param string|Command $command 指令对象或者指令类名
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
355
     * @param string         $name    指令名 留空则自动获取
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
356
     * @return Command|void
357
     */
358
    public function addCommand($command, string $name = '')
359
    {
360
        if ($name) {
361
            $this->commands[$name] = $command;
362
            return;
363
        }
364
365
        if (is_string($command)) {
366
            $command = $this->app->invokeClass($command);
367
        }
368
369
        $command->setConsole($this);
370
371
        if (!$command->isEnabled()) {
372
            $command->setConsole(null);
373
            return;
374
        }
375
376
        $command->setApp($this->app);
377
378
        if (null === $command->getDefinition()) {
379
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
380
        }
381
382
        $this->commands[$command->getName()] = $command;
383
384
        foreach ($command->getAliases() as $alias) {
385
            $this->commands[$alias] = $command;
386
        }
387
388
        return $command;
389
    }
390
391
    /**
392
     * 获取指令
393
     * @access public
394
     * @param string $name 指令名称
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
395
     * @return Command
396
     * @throws InvalidArgumentException
397
     */
398
    public function getCommand(string $name): Command
399
    {
400
        if (!isset($this->commands[$name])) {
401
            throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
402
        }
403
404
        $command = $this->commands[$name];
405
406
        if (is_string($command)) {
0 ignored issues
show
introduced by
The condition is_string($command) is always false.
Loading history...
407
            $command = $this->app->invokeClass($command);
408
            /** @var Command $command */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
409
            $command->setConsole($this);
410
            $command->setApp($this->app);
411
        }
412
413
        if ($this->wantHelps) {
414
            $this->wantHelps = false;
415
416
            /** @var HelpCommand $helpCommand */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
417
            $helpCommand = $this->getCommand('help');
418
            $helpCommand->setCommand($command);
419
420
            return $helpCommand;
421
        }
422
423
        return $command;
424
    }
425
426
    /**
427
     * 某个指令是否存在
428
     * @access public
429
     * @param string $name 指令名称
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
430
     * @return bool
431
     */
432
    public function hasCommand(string $name): bool
433
    {
434
        return isset($this->commands[$name]);
435
    }
436
437
    /**
438
     * 获取所有的命名空间
439
     * @access public
440
     * @return array
441
     */
442
    public function getNamespaces(): array
443
    {
444
        $namespaces = [];
445
        foreach ($this->commands as $command) {
446
            if (is_string($command)) {
447
                $namespaces[] = $command;
448
            } else {
449
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
450
451
                foreach ($command->getAliases() as $alias) {
452
                    $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
453
                }
454
            }
455
        }
456
457
        return array_values(array_unique(array_filter($namespaces)));
458
    }
459
460
    /**
461
     * 查找注册命名空间中的名称或缩写。
462
     * @access public
463
     * @param string $namespace
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
464
     * @return string
465
     * @throws InvalidArgumentException
466
     */
467
    public function findNamespace(string $namespace): string
468
    {
469
        $allNamespaces = $this->getNamespaces();
470
        $expr          = preg_replace_callback('{([^:]+|)}', function ($matches) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
471
            return preg_quote($matches[1]) . '[^:]*';
472
        }, $namespace);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
473
        $namespaces    = preg_grep('{^' . $expr . '}', $allNamespaces);
474
475
        if (empty($namespaces)) {
476
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
477
478
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
479
                if (1 == count($alternatives)) {
480
                    $message .= "\n\nDid you mean this?\n    ";
481
                } else {
482
                    $message .= "\n\nDid you mean one of these?\n    ";
483
                }
484
485
                $message .= implode("\n    ", $alternatives);
486
            }
487
488
            throw new InvalidArgumentException($message);
489
        }
490
491
        $exact = in_array($namespace, $namespaces, true);
492
        if (count($namespaces) > 1 && !$exact) {
493
            throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
494
        }
495
496
        return $exact ? $namespace : reset($namespaces);
497
    }
498
499
    /**
500
     * 查找指令
501
     * @access public
502
     * @param string $name 名称或者别名
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
503
     * @return Command
504
     * @throws InvalidArgumentException
505
     */
506
    public function find(string $name): Command
507
    {
508
        $allCommands = array_keys($this->commands);
509
510
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
511
            return preg_quote($matches[1]) . '[^:]*';
512
        }, $name);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
513
514
        $commands = preg_grep('{^' . $expr . '}', $allCommands);
515
516
        if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
517
            if (false !== $pos = strrpos($name, ':')) {
518
                $this->findNamespace(substr($name, 0, $pos));
519
            }
520
521
            $message = sprintf('Command "%s" is not defined.', $name);
522
523
            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
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
                $message .= implode("\n    ", $alternatives);
530
            }
531
532
            throw new InvalidArgumentException($message);
533
        }
534
535
        $exact = in_array($name, $commands, true);
536
        if (count($commands) > 1 && !$exact) {
537
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
538
539
            throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
540
        }
541
542
        return $this->getCommand($exact ? $name : reset($commands));
543
    }
544
545
    /**
546
     * 获取所有的指令
547
     * @access public
548
     * @param string $namespace 命名空间
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
549
     * @return Command[]
550
     * @api
551
     */
552
    public function all(string $namespace = null): array
553
    {
554
        if (null === $namespace) {
555
            return $this->commands;
556
        }
557
558
        $commands = [];
559
        foreach ($this->commands as $name => $command) {
560
            if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
561
                $commands[$name] = $command;
562
            }
563
        }
564
565
        return $commands;
566
    }
567
568
    /**
569
     * 配置基于用户的参数和选项的输入和输出实例。
570
     * @access protected
571
     * @param Input  $input  输入实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
572
     * @param Output $output 输出实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
573
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
574
    protected function configureIO(Input $input, Output $output): void
575
    {
576
        if (true === $input->hasParameterOption(['--ansi'])) {
577
            $output->setDecorated(true);
578
        } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
579
            $output->setDecorated(false);
580
        }
581
582
        if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
583
            $input->setInteractive(false);
584
        }
585
586
        if (true === $input->hasParameterOption(['--quiet', '-q'])) {
587
            $output->setVerbosity(Output::VERBOSITY_QUIET);
588
        } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
589
            $output->setVerbosity(Output::VERBOSITY_DEBUG);
590
        } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
591
            $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
592
        } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
593
            $output->setVerbosity(Output::VERBOSITY_VERBOSE);
594
        }
595
    }
596
597
    /**
598
     * 执行指令
599
     * @access protected
600
     * @param Command $command 指令实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
601
     * @param Input   $input   输入实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
602
     * @param Output  $output  输出实例
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
603
     * @return int
604
     * @throws \Exception
605
     */
606
    protected function doRunCommand(Command $command, Input $input, Output $output)
607
    {
608
        return $command->run($input, $output);
609
    }
610
611
    /**
612
     * 获取指令的基础名称
613
     * @access protected
614
     * @param Input $input
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
615
     * @return string
616
     */
617
    protected function getCommandName(Input $input): string
618
    {
619
        return $input->getFirstArgument() ?: '';
620
    }
621
622
    /**
623
     * 获取默认输入定义
624
     * @access protected
625
     * @return InputDefinition
626
     */
627
    protected function getDefaultInputDefinition(): InputDefinition
628
    {
629
        return new InputDefinition([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
630
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
631
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
632
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
633
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
634
            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'),
635
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
636
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
637
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
638
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
639
    }
640
641
    /**
642
     * 获取可能的建议
643
     * @access private
644
     * @param array $abbrevs
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
645
     * @return string
646
     */
647
    private function getAbbreviationSuggestions(array $abbrevs): string
0 ignored issues
show
Coding Style introduced by
Private method name "Console::getAbbreviationSuggestions" must be prefixed with an underscore
Loading history...
648
    {
649
        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
650
    }
651
652
    /**
653
     * 返回命名空间部分
654
     * @access public
655
     * @param string $name  指令
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
656
     * @param int    $limit 部分的命名空间的最大数量
1 ignored issue
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
657
     * @return string
658
     */
659
    public function extractNamespace(string $name, int $limit = 0): string
660
    {
661
        $parts = explode(':', $name);
662
        array_pop($parts);
663
664
        return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
665
    }
666
667
    /**
668
     * 查找可替代的建议
669
     * @access private
670
     * @param string             $name
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
671
     * @param array|\Traversable $collection
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
672
     * @return array
673
     */
674
    private function findAlternatives(string $name, $collection): array
0 ignored issues
show
Coding Style introduced by
Private method name "Console::findAlternatives" must be prefixed with an underscore
Loading history...
675
    {
676
        $threshold    = 1e3;
677
        $alternatives = [];
678
679
        $collectionParts = [];
680
        foreach ($collection as $item) {
681
            $collectionParts[$item] = explode(':', $item);
682
        }
683
684
        foreach (explode(':', $name) as $i => $subname) {
685
            foreach ($collectionParts as $collectionName => $parts) {
686
                $exists = isset($alternatives[$collectionName]);
687
                if (!isset($parts[$i]) && $exists) {
688
                    $alternatives[$collectionName] += $threshold;
689
                    continue;
690
                } elseif (!isset($parts[$i])) {
691
                    continue;
692
                }
693
694
                $lev = levenshtein($subname, $parts[$i]);
695
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
696
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
697
                } elseif ($exists) {
698
                    $alternatives[$collectionName] += $threshold;
699
                }
700
            }
701
        }
702
703
        foreach ($collection as $item) {
704
            $lev = levenshtein($name, $item);
705
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
706
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
707
            }
708
        }
709
710
        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
711
            return $lev < 2 * $threshold;
712
        });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
713
        asort($alternatives);
714
715
        return array_keys($alternatives);
716
    }
717
718
    /**
719
     * 返回所有的命名空间
720
     * @access private
721
     * @param string $name
1 ignored issue
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
722
     * @return array
723
     */
724
    private function extractAllNamespaces(string $name): array
0 ignored issues
show
Coding Style introduced by
Private method name "Console::extractAllNamespaces" must be prefixed with an underscore
Loading history...
725
    {
726
        $parts      = explode(':', $name, -1);
727
        $namespaces = [];
728
729
        foreach ($parts as $part) {
730
            if (count($namespaces)) {
731
                $namespaces[] = end($namespaces) . ':' . $part;
732
            } else {
733
                $namespaces[] = $part;
734
            }
735
        }
736
737
        return $namespaces;
738
    }
739
740
}
741