Passed
Push — 5.2 ( af64fa...8b63b2 )
by
unknown
02:49
created

Console::hasCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

85
            $this->setUser(/** @scrutinizer ignore-type */ $user);
Loading history...
86
        }
87
88
        $this->definition = $this->getDefaultInputDefinition();
89
90
        //加载指令
91
        $this->loadCommands();
92
    }
93
94
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $user should have a doc-comment as per coding-style.
Loading history...
95
     * 设置执行用户
96
     * @param $user
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
97
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
98
    protected function setUser(string $user)
99
    {
100
        if (DIRECTORY_SEPARATOR == '\\') {
101
            return;
102
        }
103
104
        $user = posix_getpwnam($user);
105
106
        if (!empty($user)) {
107
            posix_setuid($user['uid']);
108
            posix_setgid($user['gid']);
109
        }
110
    }
111
112
    /**
113
     * 加载指令
114
     * @access protected
115
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
116
    protected function loadCommands()
117
    {
118
        //加载默认指令
119
        $this->addCommands($this->defaultCommands);
120
121
        $path = $this->app->getAppPath();
122
123
        if (is_dir($path . 'command')) {
124
            // 自动加载指令类
125
            $files = glob($path . 'command' . DIRECTORY_SEPARATOR . '*.php');
126
127
            if (!empty($files)) {
128
                $beforeClass = get_declared_classes();
129
130
                foreach ($files as $file) {
131
                    include $file;
132
                }
133
134
                $afterClass = get_declared_classes();
135
                $commands   = array_diff($afterClass, $beforeClass);
136
137
                $this->addCommands($commands);
138
            }
139
        }
140
141
        $commands = $this->app->config->get('console.commands', []);
142
143
        $this->addCommands($commands);
144
    }
145
146
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
147
     * @access public
148
     * @param  string $command
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
149
     * @param  array  $parameters
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
150
     * @param  string $driver
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
151
     * @return Output|Buffer
152
     */
153
    public function call(string $command, array $parameters = [], string $driver = 'buffer')
154
    {
155
        array_unshift($parameters, $command);
156
157
        $input  = new Input($parameters);
158
        $output = new Output($driver);
159
160
        $this->setCatchExceptions(false);
161
        $this->find($command)->run($input, $output);
162
163
        return $output;
164
    }
165
166
    /**
167
     * 执行当前的指令
168
     * @access public
169
     * @return int
170
     * @throws \Exception
171
     * @api
172
     */
173
    public function run()
174
    {
175
        $input  = new Input();
176
        $output = new Output();
177
178
        $this->configureIO($input, $output);
179
180
        try {
181
            $exitCode = $this->doRun($input, $output);
182
        } catch (\Exception $e) {
183
            if (!$this->catchExceptions) {
184
                throw $e;
185
            }
186
187
            $output->renderException($e);
188
189
            $exitCode = $e->getCode();
190
            if (is_numeric($exitCode)) {
191
                $exitCode = (int)$exitCode;
192
                if (0 === $exitCode) {
193
                    $exitCode = 1;
194
                }
195
            } else {
196
                $exitCode = 1;
197
            }
198
        }
199
200
        if ($this->autoExit) {
201
            if ($exitCode > 255) {
202
                $exitCode = 255;
203
            }
204
205
            exit($exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
206
        }
207
208
        return $exitCode;
209
    }
210
211
    /**
212
     * 执行指令
213
     * @access public
214
     * @param  Input  $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
215
     * @param  Output $output
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
216
     * @return int
217
     */
218
    public function doRun(Input $input, Output $output)
219
    {
220
        if (true === $input->hasParameterOption(['--version', '-V'])) {
221
            $output->writeln($this->getLongVersion());
222
223
            return 0;
224
        }
225
226
        $name = $this->getCommandName($input);
227
228
        if (true === $input->hasParameterOption(['--help', '-h'])) {
229
            if (!$name) {
230
                $name  = 'help';
231
                $input = new Input(['help']);
232
            } else {
233
                $this->wantHelps = true;
234
            }
235
        }
236
237
        if (!$name) {
238
            $name  = $this->defaultCommand;
239
            $input = new Input([$this->defaultCommand]);
240
        }
241
242
        $command = $this->find($name);
243
244
        return $this->doRunCommand($command, $input, $output);
245
    }
246
247
    /**
248
     * 设置输入参数定义
249
     * @access public
250
     * @param  InputDefinition $definition
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
251
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
252
    public function setDefinition(InputDefinition $definition)
253
    {
254
        $this->definition = $definition;
255
    }
256
257
    /**
258
     * 获取输入参数定义
259
     * @access public
260
     * @return InputDefinition The InputDefinition instance
261
     */
262
    public function getDefinition()
263
    {
264
        return $this->definition;
265
    }
266
267
    /**
268
     * Gets the help message.
269
     * @access public
270
     * @return string A help message.
271
     */
272
    public function getHelp()
273
    {
274
        return $this->getLongVersion();
275
    }
276
277
    /**
278
     * 是否捕获异常
279
     * @access public
280
     * @param  bool $boolean
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
281
     * @api
282
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
283
    public function setCatchExceptions(bool $boolean)
284
    {
285
        $this->catchExceptions = $boolean;
286
    }
287
288
    /**
289
     * 是否自动退出
290
     * @access public
291
     * @param  bool $boolean
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
292
     * @api
293
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
294
    public function setAutoExit(bool $boolean)
295
    {
296
        $this->autoExit = $boolean;
297
    }
298
299
300
    /**
301
     * 获取完整的版本号
302
     * @access public
303
     * @return string
304
     */
305
    public function getLongVersion()
306
    {
307
        if ('UNKNOWN' !== $this->app->getName() && 'UNKNOWN' !== $this->app->version()) {
308
            return sprintf('<info>%s</info> version <comment>%s</comment>', $this->app->getName(), $this->app->version());
309
        }
310
311
        return '<info>Console Tool</info>';
312
    }
313
314
    /**
315
     * 注册一个指令 (便于动态创建指令)
316
     * @access public
317
     * @param  string $name 指令名
318
     * @return Command
319
     */
320
    public function register(string $name)
321
    {
322
        return $this->addCommand(new Command($name));
323
    }
324
325
    /**
326
     * 添加指令集
327
     * @access public
328
     * @param  array $commands
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
329
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
330
    public function addCommands(array $commands)
331
    {
332
        foreach ($commands as $key => $command) {
333
            if (is_subclass_of($command, "\\think\\console\\Command")) {
334
                // 注册指令
335
                $this->addCommand($command, is_numeric($key) ? '' : $key);
336
            }
337
        }
338
    }
339
340
    /**
341
     * 添加一个指令
342
     * @access public
343
     * @param  mixed  $command 指令对象或者指令类名
344
     * @param  string $name    指令名 留空则自动获取
345
     * @return Command
346
     */
347
    public function addCommand($command, string $name = '')
348
    {
349
        if ($name) {
350
            $this->commands[$name] = $command;
351
            return;
352
        }
353
354
        if (is_string($command)) {
355
            $command = new $command();
356
        }
357
358
        $command->setConsole($this);
359
360
        if (!$command->isEnabled()) {
361
            $command->setConsole(null);
362
            return;
363
        }
364
365
        if (null === $command->getDefinition()) {
366
            throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
367
        }
368
369
        $this->commands[$command->getName()] = $command;
370
371
        foreach ($command->getAliases() as $alias) {
372
            $this->commands[$alias] = $command;
373
        }
374
375
        return $command;
376
    }
377
378
    /**
379
     * 获取指令
380
     * @access public
381
     * @param  string $name 指令名称
382
     * @return Command
383
     * @throws \InvalidArgumentException
384
     */
385
    public function getCommand(string $name)
386
    {
387
        if (!isset($this->commands[$name])) {
388
            throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
389
        }
390
391
        $command = $this->commands[$name];
392
393
        if (is_string($command)) {
0 ignored issues
show
introduced by
The condition is_string($command) is always false.
Loading history...
394
            $command = new $command();
395
        }
396
397
        $command->setConsole($this);
398
399
        if ($this->wantHelps) {
400
            $this->wantHelps = false;
401
402
            /** @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...
403
            $helpCommand = $this->getCommand('help');
404
            $helpCommand->setCommand($command);
405
406
            return $helpCommand;
407
        }
408
409
        return $command;
410
    }
411
412
    /**
413
     * 某个指令是否存在
414
     * @access public
415
     * @param  string $name 指令名称
416
     * @return bool
417
     */
418
    public function hasCommand(string $name)
419
    {
420
        return isset($this->commands[$name]);
421
    }
422
423
    /**
424
     * 获取所有的命名空间
425
     * @access public
426
     * @return array
427
     */
428
    public function getNamespaces()
429
    {
430
        $namespaces = [];
431
        foreach ($this->commands as $command) {
432
            if (is_string($command)) {
433
                $namespaces[] = $command;
434
            } else {
435
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
436
437
                foreach ($command->getAliases() as $alias) {
438
                    $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
439
                }
440
            }
441
        }
442
443
        return array_values(array_unique(array_filter($namespaces)));
444
    }
445
446
    /**
447
     * 查找注册命名空间中的名称或缩写。
448
     * @access public
449
     * @param  string $namespace
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
450
     * @return string
451
     * @throws \InvalidArgumentException
452
     */
453
    public function findNamespace(string $namespace)
454
    {
455
        $allNamespaces = $this->getNamespaces();
456
        $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...
457
            return preg_quote($matches[1]) . '[^:]*';
458
        }, $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...
459
        $namespaces    = preg_grep('{^' . $expr . '}', $allNamespaces);
460
461
        if (empty($namespaces)) {
462
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
463
464
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
465
                if (1 == count($alternatives)) {
466
                    $message .= "\n\nDid you mean this?\n    ";
467
                } else {
468
                    $message .= "\n\nDid you mean one of these?\n    ";
469
                }
470
471
                $message .= implode("\n    ", $alternatives);
472
            }
473
474
            throw new \InvalidArgumentException($message);
475
        }
476
477
        $exact = in_array($namespace, $namespaces, true);
478
        if (count($namespaces) > 1 && !$exact) {
479
            throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
480
        }
481
482
        return $exact ? $namespace : reset($namespaces);
483
    }
484
485
    /**
486
     * 查找指令
487
     * @access public
488
     * @param  string $name 名称或者别名
489
     * @return Command
490
     * @throws \InvalidArgumentException
491
     */
492
    public function find(string $name)
493
    {
494
        $allCommands = array_keys($this->commands);
495
496
        $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...
497
            return preg_quote($matches[1]) . '[^:]*';
498
        }, $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...
499
500
        $commands = preg_grep('{^' . $expr . '}', $allCommands);
501
502
        if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
503
            if (false !== $pos = strrpos($name, ':')) {
504
                $this->findNamespace(substr($name, 0, $pos));
505
            }
506
507
            $message = sprintf('Command "%s" is not defined.', $name);
508
509
            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
510
                if (1 == count($alternatives)) {
511
                    $message .= "\n\nDid you mean this?\n    ";
512
                } else {
513
                    $message .= "\n\nDid you mean one of these?\n    ";
514
                }
515
                $message .= implode("\n    ", $alternatives);
516
            }
517
518
            throw new \InvalidArgumentException($message);
519
        }
520
521
        $exact = in_array($name, $commands, true);
522
        if (count($commands) > 1 && !$exact) {
523
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
524
525
            throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
526
        }
527
528
        return $this->get($exact ? $name : reset($commands));
0 ignored issues
show
Bug introduced by
The method get() does not exist on think\Console. ( Ignorable by Annotation )

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

528
        return $this->/** @scrutinizer ignore-call */ get($exact ? $name : reset($commands));

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

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

Loading history...
529
    }
530
531
    /**
532
     * 获取所有的指令
533
     * @access public
534
     * @param  string $namespace 命名空间
535
     * @return Command[]
536
     * @api
537
     */
538
    public function all(string $namespace = null)
539
    {
540
        if (null === $namespace) {
541
            return $this->commands;
542
        }
543
544
        $commands = [];
545
        foreach ($this->commands as $name => $command) {
546
            if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
547
                $commands[$name] = $command;
548
            }
549
        }
550
551
        return $commands;
552
    }
553
554
    /**
555
     * 获取可能的指令名
556
     * @access public
557
     * @param  array $names
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
558
     * @return array
559
     */
560
    public static function getAbbreviations(array $names)
561
    {
562
        $abbrevs = [];
563
        foreach ($names as $name) {
564
            for ($len = strlen($name); $len > 0; --$len) {
565
                $abbrev             = substr($name, 0, $len);
566
                $abbrevs[$abbrev][] = $name;
567
            }
568
        }
569
570
        return $abbrevs;
571
    }
572
573
    /**
574
     * 配置基于用户的参数和选项的输入和输出实例。
575
     * @access protected
576
     * @param  Input  $input  输入实例
577
     * @param  Output $output 输出实例
578
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
579
    protected function configureIO(Input $input, Output $output)
580
    {
581
        if (true === $input->hasParameterOption(['--ansi'])) {
582
            $output->setDecorated(true);
583
        } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
584
            $output->setDecorated(false);
585
        }
586
587
        if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
588
            $input->setInteractive(false);
589
        }
590
591
        if (true === $input->hasParameterOption(['--quiet', '-q'])) {
592
            $output->setVerbosity(Output::VERBOSITY_QUIET);
593
        } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
594
            $output->setVerbosity(Output::VERBOSITY_DEBUG);
595
        } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
596
            $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
597
        } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
598
            $output->setVerbosity(Output::VERBOSITY_VERBOSE);
599
        }
600
    }
601
602
    /**
603
     * 执行指令
604
     * @access protected
605
     * @param  Command $command 指令实例
606
     * @param  Input   $input   输入实例
607
     * @param  Output  $output  输出实例
608
     * @return int
609
     * @throws \Exception
610
     */
611
    protected function doRunCommand(Command $command, Input $input, Output $output)
612
    {
613
        return $command->run($input, $output);
614
    }
615
616
    /**
617
     * 获取指令的基础名称
618
     * @access protected
619
     * @param  Input $input
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
620
     * @return string
621
     */
622
    protected function getCommandName(Input $input)
623
    {
624
        return $input->getFirstArgument();
625
    }
626
627
    /**
628
     * 获取默认输入定义
629
     * @access protected
630
     * @return InputDefinition
631
     */
632
    protected function getDefaultInputDefinition()
633
    {
634
        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...
635
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
636
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
637
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
638
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
639
            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'),
640
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
641
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
642
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
643
        ]);
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...
644
    }
645
646
    /**
647
     * 获取可能的建议
648
     * @access private
649
     * @param  array $abbrevs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
650
     * @return string
651
     */
652
    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...
653
    {
654
        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
655
    }
656
657
    /**
658
     * 返回命名空间部分
659
     * @access public
660
     * @param  string $name  指令
661
     * @param  int    $limit 部分的命名空间的最大数量
662
     * @return string
663
     */
664
    public function extractNamespace(string $name, int $limit = 0)
665
    {
666
        $parts = explode(':', $name);
667
        array_pop($parts);
668
669
        return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
670
    }
671
672
    /**
673
     * 查找可替代的建议
674
     * @access private
675
     * @param  string             $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
676
     * @param  array|\Traversable $collection
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
677
     * @return array
678
     */
679
    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...
680
    {
681
        $threshold    = 1e3;
682
        $alternatives = [];
683
684
        $collectionParts = [];
685
        foreach ($collection as $item) {
686
            $collectionParts[$item] = explode(':', $item);
687
        }
688
689
        foreach (explode(':', $name) as $i => $subname) {
690
            foreach ($collectionParts as $collectionName => $parts) {
691
                $exists = isset($alternatives[$collectionName]);
692
                if (!isset($parts[$i]) && $exists) {
693
                    $alternatives[$collectionName] += $threshold;
694
                    continue;
695
                } elseif (!isset($parts[$i])) {
696
                    continue;
697
                }
698
699
                $lev = levenshtein($subname, $parts[$i]);
700
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
701
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
702
                } elseif ($exists) {
703
                    $alternatives[$collectionName] += $threshold;
704
                }
705
            }
706
        }
707
708
        foreach ($collection as $item) {
709
            $lev = levenshtein($name, $item);
710
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
711
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
712
            }
713
        }
714
715
        $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...
716
            return $lev < 2 * $threshold;
717
        });
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...
718
        asort($alternatives);
719
720
        return array_keys($alternatives);
721
    }
722
723
    /**
724
     * 返回所有的命名空间
725
     * @access private
726
     * @param  string $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
727
     * @return array
728
     */
729
    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...
730
    {
731
        $parts      = explode(':', $name, -1);
732
        $namespaces = [];
733
734
        foreach ($parts as $part) {
735
            if (count($namespaces)) {
736
                $namespaces[] = end($namespaces) . ':' . $part;
737
            } else {
738
                $namespaces[] = $part;
739
            }
740
        }
741
742
        return $namespaces;
743
    }
744
745
}
746