Passed
Push — 5.2 ( c6dbca...184417 )
by
unknown
02:35
created

Console::addCommand()   B

Complexity

Conditions 8
Paths 19

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 21
nc 19
nop 2
dl 0
loc 40
rs 8.4444
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 think\console\Command;
15
use think\console\command\Clear;
16
use think\console\command\Help;
17
use think\console\command\Help as HelpCommand;
18
use think\console\command\Lists;
19
use think\console\command\make\Command as MakeCommand;
20
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...
21
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...
22
use think\console\command\make\Listener;
23
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...
24
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...
25
use think\console\command\make\Subscribe;
26
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...
27
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...
28
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...
29
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...
30
use think\console\command\optimize\Schema;
31
use think\console\command\RouteList;
32
use think\console\command\RunServer;
33
use think\console\command\ServiceDiscover;
34
use think\console\command\Version;
35
use think\console\Input;
36
use think\console\input\Argument as InputArgument;
37
use think\console\input\Definition as InputDefinition;
38
use think\console\input\Option as InputOption;
39
use think\console\Output;
40
use think\console\output\driver\Buffer;
41
42
class Console
1 ignored issue
show
Coding Style introduced by
Missing class doc comment
Loading history...
43
{
44
45
    protected $app;
46
47
    /** @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...
48
    protected $commands = [];
49
50
    protected $wantHelps = false;
51
52
    protected $catchExceptions = true;
53
    protected $autoExit        = true;
54
    protected $definition;
55
    protected $defaultCommand  = 'list';
56
57
    protected $defaultCommands = [
58
        'help'             => Help::class,
59
        'list'             => Lists::class,
60
        'build'            => Build::class,
61
        'clear'            => Clear::class,
62
        'make:command'     => MakeCommand::class,
63
        'make:controller'  => Controller::class,
64
        'make:model'       => Model::class,
65
        'make:middleware'  => Middleware::class,
66
        'make:validate'    => Validate::class,
67
        'make:event'       => Event::class,
68
        'make:listener'    => Listener::class,
69
        'make:subscribe'   => Subscribe::class,
70
        'optimize:config'  => Config::class,
71
        'optimize:schema'  => Schema::class,
72
        'optimize:route'   => Route::class,
73
        'optimize:facade'  => Facade::class,
74
        'run'              => RunServer::class,
75
        'version'          => Version::class,
76
        'route:list'       => RouteList::class,
77
        'service:discover' => ServiceDiscover::class
78
    ];
79
80
    /**
81
     * 初始化器
82
     * @var array
83
     */
84
    static protected $initializers = [];
85
86
    public function __construct(App $app)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
87
    {
88
        $this->app = $app;
89
90
91
        $user = $this->app->config->get('console.user');
92
93
        if ($user) {
94
            $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

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