top-think /
framework
| 1 | <?php |
||||
| 2 | // +---------------------------------------------------------------------- |
||||
| 3 | // | TopThink [ WE CAN DO IT JUST THINK IT ] |
||||
| 4 | // +---------------------------------------------------------------------- |
||||
| 5 | // | Copyright (c) 2015 http://www.topthink.com All rights reserved. |
||||
| 6 | // +---------------------------------------------------------------------- |
||||
| 7 | // | Author: zhangyajun <[email protected]> |
||||
| 8 | // +---------------------------------------------------------------------- |
||||
| 9 | declare (strict_types = 1); |
||||
| 10 | |||||
| 11 | namespace think; |
||||
| 12 | |||||
| 13 | use Closure; |
||||
| 14 | use InvalidArgumentException; |
||||
| 15 | use LogicException; |
||||
| 16 | use think\console\Command; |
||||
| 17 | use think\console\command\Clear; |
||||
| 18 | use think\console\command\Help; |
||||
| 19 | use think\console\command\Help as HelpCommand; |
||||
| 20 | use think\console\command\Lists; |
||||
| 21 | use think\console\command\make\Command as MakeCommand; |
||||
| 22 | use think\console\command\make\Controller; |
||||
| 23 | use think\console\command\make\Event; |
||||
|
0 ignored issues
–
show
|
|||||
| 24 | use think\console\command\make\Listener; |
||||
| 25 | use think\console\command\make\Middleware; |
||||
|
0 ignored issues
–
show
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
namespace OtherDir;
use SomeDir\Foo; // This now conflicts the class OtherDir\Foo
If both files PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as // Bar.php
namespace OtherDir;
use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
|
|||||
| 26 | use think\console\command\make\Model; |
||||
|
0 ignored issues
–
show
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
namespace OtherDir;
use SomeDir\Foo; // This now conflicts the class OtherDir\Foo
If both files PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as // Bar.php
namespace OtherDir;
use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
|
|||||
| 27 | use think\console\command\make\Service; |
||||
|
0 ignored issues
–
show
This use statement conflicts with another class in this namespace,
think\Service. Consider defining an alias.
Let?s assume that you have a directory layout like this: .
|-- OtherDir
| |-- Bar.php
| `-- Foo.php
`-- SomeDir
`-- Foo.php
and let?s assume the following content of // Bar.php
namespace OtherDir;
use SomeDir\Foo; // This now conflicts the class OtherDir\Foo
If both files PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as // 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
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
namespace OtherDir;
use SomeDir\Foo; // This now conflicts the class OtherDir\Foo
If both files PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as // Bar.php
namespace OtherDir;
use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
|
|||||
| 30 | use think\console\command\optimize\Route; |
||||
|
0 ignored issues
–
show
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
namespace OtherDir;
use SomeDir\Foo; // This now conflicts the class OtherDir\Foo
If both files PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as // Bar.php
namespace OtherDir;
use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
|
|||||
| 31 | use think\console\command\optimize\Schema; |
||||
| 32 | use think\console\command\RouteList; |
||||
| 33 | use think\console\command\RunServer; |
||||
| 34 | use think\console\command\ServiceDiscover; |
||||
| 35 | use think\console\command\VendorPublish; |
||||
| 36 | use think\console\command\Version; |
||||
| 37 | use think\console\Input; |
||||
| 38 | use think\console\input\Argument as InputArgument; |
||||
| 39 | use think\console\input\Definition as InputDefinition; |
||||
| 40 | use think\console\input\Option as InputOption; |
||||
| 41 | use think\console\Output; |
||||
| 42 | use think\console\output\driver\Buffer; |
||||
| 43 | |||||
| 44 | /** |
||||
| 45 | * 控制台应用管理类 |
||||
| 46 | */ |
||||
| 47 | class Console |
||||
| 48 | { |
||||
| 49 | |||||
| 50 | protected $app; |
||||
| 51 | |||||
| 52 | /** @var Command[] */ |
||||
| 53 | protected $commands = []; |
||||
| 54 | |||||
| 55 | protected $wantHelps = false; |
||||
| 56 | |||||
| 57 | protected $catchExceptions = true; |
||||
| 58 | protected $autoExit = true; |
||||
| 59 | protected $definition; |
||||
| 60 | protected $defaultCommand = 'list'; |
||||
| 61 | |||||
| 62 | protected $defaultCommands = [ |
||||
| 63 | 'help' => Help::class, |
||||
| 64 | 'list' => Lists::class, |
||||
| 65 | 'clear' => Clear::class, |
||||
| 66 | 'make:command' => MakeCommand::class, |
||||
| 67 | 'make:controller' => Controller::class, |
||||
| 68 | 'make:model' => Model::class, |
||||
| 69 | 'make:middleware' => Middleware::class, |
||||
| 70 | 'make:validate' => Validate::class, |
||||
| 71 | 'make:event' => Event::class, |
||||
| 72 | 'make:listener' => Listener::class, |
||||
| 73 | 'make:service' => Service::class, |
||||
| 74 | 'make:subscribe' => Subscribe::class, |
||||
| 75 | 'optimize:route' => Route::class, |
||||
| 76 | 'optimize:schema' => Schema::class, |
||||
| 77 | 'run' => RunServer::class, |
||||
| 78 | 'version' => Version::class, |
||||
| 79 | 'route:list' => RouteList::class, |
||||
| 80 | 'service:discover' => ServiceDiscover::class, |
||||
| 81 | 'vendor:publish' => VendorPublish::class, |
||||
| 82 | ]; |
||||
| 83 | |||||
| 84 | /** |
||||
| 85 | * 启动器 |
||||
| 86 | * @var array |
||||
| 87 | */ |
||||
| 88 | protected static $startCallbacks = []; |
||||
| 89 | |||||
| 90 | public function __construct(App $app) |
||||
| 91 | { |
||||
| 92 | $this->app = $app; |
||||
| 93 | |||||
| 94 | $this->initialize(); |
||||
| 95 | |||||
| 96 | $this->definition = $this->getDefaultInputDefinition(); |
||||
| 97 | |||||
| 98 | //加载指令 |
||||
| 99 | $this->loadCommands(); |
||||
| 100 | |||||
| 101 | $this->start(); |
||||
| 102 | } |
||||
| 103 | |||||
| 104 | /** |
||||
| 105 | * 初始化 |
||||
| 106 | */ |
||||
| 107 | protected function initialize() |
||||
| 108 | { |
||||
| 109 | if (!$this->app->initialized()) { |
||||
| 110 | $this->app->initialize(); |
||||
| 111 | } |
||||
| 112 | $this->makeRequest(); |
||||
| 113 | } |
||||
| 114 | |||||
| 115 | /** |
||||
| 116 | * 构造request |
||||
| 117 | */ |
||||
| 118 | protected function makeRequest() |
||||
| 119 | { |
||||
| 120 | $url = $this->app->config->get('app.url', 'http://localhost'); |
||||
| 121 | |||||
| 122 | $components = parse_url($url); |
||||
|
0 ignored issues
–
show
It seems like
$url can also be of type array; however, parameter $url of parse_url() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 123 | |||||
| 124 | $server = $_SERVER; |
||||
| 125 | |||||
| 126 | if (isset($components['path'])) { |
||||
| 127 | $server = array_merge($server, [ |
||||
| 128 | 'SCRIPT_FILENAME' => $components['path'], |
||||
| 129 | 'SCRIPT_NAME' => $components['path'], |
||||
| 130 | 'REQUEST_URI' => $components['path'], |
||||
| 131 | ]); |
||||
| 132 | } |
||||
| 133 | |||||
| 134 | if (isset($components['host'])) { |
||||
| 135 | $server['SERVER_NAME'] = $components['host']; |
||||
| 136 | $server['HTTP_HOST'] = $components['host']; |
||||
| 137 | } |
||||
| 138 | |||||
| 139 | if (isset($components['scheme'])) { |
||||
| 140 | if ('https' === $components['scheme']) { |
||||
| 141 | $server['HTTPS'] = 'on'; |
||||
| 142 | $server['SERVER_PORT'] = 443; |
||||
| 143 | } else { |
||||
| 144 | unset($server['HTTPS']); |
||||
| 145 | $server['SERVER_PORT'] = 80; |
||||
| 146 | } |
||||
| 147 | } |
||||
| 148 | |||||
| 149 | if (isset($components['port'])) { |
||||
| 150 | $server['SERVER_PORT'] = $components['port']; |
||||
| 151 | $server['HTTP_HOST'] .= ':' . $components['port']; |
||||
| 152 | } |
||||
| 153 | |||||
| 154 | /** @var Request $request */ |
||||
| 155 | $request = $this->app->make('request'); |
||||
| 156 | |||||
| 157 | $request->withServer($server); |
||||
| 158 | } |
||||
| 159 | |||||
| 160 | /** |
||||
| 161 | * 添加初始化器 |
||||
| 162 | * @param Closure $callback |
||||
| 163 | */ |
||||
| 164 | public static function starting(Closure $callback): void |
||||
| 165 | { |
||||
| 166 | static::$startCallbacks[] = $callback; |
||||
| 167 | } |
||||
| 168 | |||||
| 169 | /** |
||||
| 170 | * 清空启动器 |
||||
| 171 | */ |
||||
| 172 | public static function flushStartCallbacks(): void |
||||
| 173 | { |
||||
| 174 | static::$startCallbacks = []; |
||||
| 175 | } |
||||
| 176 | |||||
| 177 | /** |
||||
| 178 | * 设置执行用户 |
||||
| 179 | * @param $user |
||||
| 180 | */ |
||||
| 181 | public static function setUser(string $user): void |
||||
| 182 | { |
||||
| 183 | if (extension_loaded('posix')) { |
||||
| 184 | $user = posix_getpwnam($user); |
||||
| 185 | |||||
| 186 | if (!empty($user)) { |
||||
| 187 | posix_setgid($user['gid']); |
||||
| 188 | posix_setuid($user['uid']); |
||||
| 189 | } |
||||
| 190 | } |
||||
| 191 | } |
||||
| 192 | |||||
| 193 | /** |
||||
| 194 | * 启动 |
||||
| 195 | */ |
||||
| 196 | protected function start(): void |
||||
| 197 | { |
||||
| 198 | foreach (static::$startCallbacks as $callback) { |
||||
| 199 | $callback($this); |
||||
| 200 | } |
||||
| 201 | } |
||||
| 202 | |||||
| 203 | /** |
||||
| 204 | * 加载指令 |
||||
| 205 | * @access protected |
||||
| 206 | */ |
||||
| 207 | protected function loadCommands(): void |
||||
| 208 | { |
||||
| 209 | $commands = $this->app->config->get('console.commands', []); |
||||
| 210 | $commands = array_merge($this->defaultCommands, $commands); |
||||
| 211 | |||||
| 212 | $this->addCommands($commands); |
||||
| 213 | } |
||||
| 214 | |||||
| 215 | /** |
||||
| 216 | * @access public |
||||
| 217 | * @param string $command |
||||
| 218 | * @param array $parameters |
||||
| 219 | * @param string $driver |
||||
| 220 | * @return Output|Buffer |
||||
| 221 | */ |
||||
| 222 | public function call(string $command, array $parameters = [], string $driver = 'buffer') |
||||
| 223 | { |
||||
| 224 | array_unshift($parameters, $command); |
||||
| 225 | |||||
| 226 | $input = new Input($parameters); |
||||
| 227 | $output = new Output($driver); |
||||
| 228 | |||||
| 229 | $this->setCatchExceptions(false); |
||||
| 230 | $this->find($command)->run($input, $output); |
||||
| 231 | |||||
| 232 | return $output; |
||||
| 233 | } |
||||
| 234 | |||||
| 235 | /** |
||||
| 236 | * 执行当前的指令 |
||||
| 237 | * @access public |
||||
| 238 | * @return int |
||||
| 239 | * @throws \Exception |
||||
| 240 | * @api |
||||
| 241 | */ |
||||
| 242 | public function run() |
||||
| 243 | { |
||||
| 244 | $input = new Input(); |
||||
| 245 | $output = new Output(); |
||||
| 246 | |||||
| 247 | $this->configureIO($input, $output); |
||||
| 248 | |||||
| 249 | try { |
||||
| 250 | $exitCode = $this->doRun($input, $output); |
||||
| 251 | } catch (\Exception $e) { |
||||
| 252 | if (!$this->catchExceptions) { |
||||
| 253 | throw $e; |
||||
| 254 | } |
||||
| 255 | |||||
| 256 | $output->renderException($e); |
||||
| 257 | |||||
| 258 | $exitCode = $e->getCode(); |
||||
| 259 | if (is_numeric($exitCode)) { |
||||
| 260 | $exitCode = (int) $exitCode; |
||||
| 261 | if (0 === $exitCode) { |
||||
| 262 | $exitCode = 1; |
||||
| 263 | } |
||||
| 264 | } else { |
||||
| 265 | $exitCode = 1; |
||||
| 266 | } |
||||
| 267 | } |
||||
| 268 | |||||
| 269 | if ($this->autoExit) { |
||||
| 270 | if ($exitCode > 255) { |
||||
| 271 | $exitCode = 255; |
||||
| 272 | } |
||||
| 273 | |||||
| 274 | exit($exitCode); |
||||
|
0 ignored issues
–
show
|
|||||
| 275 | } |
||||
| 276 | |||||
| 277 | return $exitCode; |
||||
| 278 | } |
||||
| 279 | |||||
| 280 | /** |
||||
| 281 | * 执行指令 |
||||
| 282 | * @access public |
||||
| 283 | * @param Input $input |
||||
| 284 | * @param Output $output |
||||
| 285 | * @return int |
||||
| 286 | */ |
||||
| 287 | public function doRun(Input $input, Output $output) |
||||
| 288 | { |
||||
| 289 | if (true === $input->hasParameterOption(['--version', '-V'])) { |
||||
| 290 | $output->writeln($this->getLongVersion()); |
||||
| 291 | |||||
| 292 | return 0; |
||||
| 293 | } |
||||
| 294 | |||||
| 295 | $name = $this->getCommandName($input); |
||||
| 296 | |||||
| 297 | if (true === $input->hasParameterOption(['--help', '-h'])) { |
||||
| 298 | if (!$name) { |
||||
| 299 | $name = 'help'; |
||||
| 300 | $input = new Input(['help']); |
||||
| 301 | } else { |
||||
| 302 | $this->wantHelps = true; |
||||
| 303 | } |
||||
| 304 | } |
||||
| 305 | |||||
| 306 | if (!$name) { |
||||
| 307 | $name = $this->defaultCommand; |
||||
| 308 | $input = new Input([$this->defaultCommand]); |
||||
| 309 | } |
||||
| 310 | |||||
| 311 | $command = $this->find($name); |
||||
| 312 | |||||
| 313 | return $this->doRunCommand($command, $input, $output); |
||||
| 314 | } |
||||
| 315 | |||||
| 316 | /** |
||||
| 317 | * 设置输入参数定义 |
||||
| 318 | * @access public |
||||
| 319 | * @param InputDefinition $definition |
||||
| 320 | */ |
||||
| 321 | public function setDefinition(InputDefinition $definition): void |
||||
| 322 | { |
||||
| 323 | $this->definition = $definition; |
||||
| 324 | } |
||||
| 325 | |||||
| 326 | /** |
||||
| 327 | * 获取输入参数定义 |
||||
| 328 | * @access public |
||||
| 329 | * @return InputDefinition The InputDefinition instance |
||||
| 330 | */ |
||||
| 331 | public function getDefinition(): InputDefinition |
||||
| 332 | { |
||||
| 333 | return $this->definition; |
||||
| 334 | } |
||||
| 335 | |||||
| 336 | /** |
||||
| 337 | * Gets the help message. |
||||
| 338 | * @access public |
||||
| 339 | * @return string A help message. |
||||
| 340 | */ |
||||
| 341 | public function getHelp(): string |
||||
| 342 | { |
||||
| 343 | return $this->getLongVersion(); |
||||
| 344 | } |
||||
| 345 | |||||
| 346 | /** |
||||
| 347 | * 是否捕获异常 |
||||
| 348 | * @access public |
||||
| 349 | * @param bool $boolean |
||||
| 350 | * @api |
||||
| 351 | */ |
||||
| 352 | public function setCatchExceptions(bool $boolean): void |
||||
| 353 | { |
||||
| 354 | $this->catchExceptions = $boolean; |
||||
| 355 | } |
||||
| 356 | |||||
| 357 | /** |
||||
| 358 | * 是否自动退出 |
||||
| 359 | * @access public |
||||
| 360 | * @param bool $boolean |
||||
| 361 | * @api |
||||
| 362 | */ |
||||
| 363 | public function setAutoExit(bool $boolean): void |
||||
| 364 | { |
||||
| 365 | $this->autoExit = $boolean; |
||||
| 366 | } |
||||
| 367 | |||||
| 368 | /** |
||||
| 369 | * 获取完整的版本号 |
||||
| 370 | * @access public |
||||
| 371 | * @return string |
||||
| 372 | */ |
||||
| 373 | public function getLongVersion(): string |
||||
| 374 | { |
||||
| 375 | if ($this->app->version()) { |
||||
| 376 | return sprintf('version <comment>%s</comment>', $this->app->version()); |
||||
| 377 | } |
||||
| 378 | |||||
| 379 | return '<info>Console Tool</info>'; |
||||
| 380 | } |
||||
| 381 | |||||
| 382 | /** |
||||
| 383 | * 添加指令集 |
||||
| 384 | * @access public |
||||
| 385 | * @param array $commands |
||||
| 386 | */ |
||||
| 387 | public function addCommands(array $commands): void |
||||
| 388 | { |
||||
| 389 | foreach ($commands as $key => $command) { |
||||
| 390 | if (is_subclass_of($command, Command::class)) { |
||||
| 391 | // 注册指令 |
||||
| 392 | $this->addCommand($command, is_numeric($key) ? '' : $key); |
||||
| 393 | } |
||||
| 394 | } |
||||
| 395 | } |
||||
| 396 | |||||
| 397 | /** |
||||
| 398 | * 添加一个指令 |
||||
| 399 | * @access public |
||||
| 400 | * @param string|Command $command 指令对象或者指令类名 |
||||
| 401 | * @param string $name 指令名 留空则自动获取 |
||||
| 402 | * @return Command|void |
||||
| 403 | */ |
||||
| 404 | public function addCommand($command, string $name = '') |
||||
| 405 | { |
||||
| 406 | if ($name) { |
||||
| 407 | $this->commands[$name] = $command; |
||||
| 408 | return; |
||||
| 409 | } |
||||
| 410 | |||||
| 411 | if (is_string($command)) { |
||||
| 412 | $command = $this->app->invokeClass($command); |
||||
| 413 | } |
||||
| 414 | |||||
| 415 | $command->setConsole($this); |
||||
| 416 | |||||
| 417 | if (!$command->isEnabled()) { |
||||
| 418 | $command->setConsole(null); |
||||
| 419 | return; |
||||
| 420 | } |
||||
| 421 | |||||
| 422 | $command->setApp($this->app); |
||||
| 423 | |||||
| 424 | if (null === $command->getDefinition()) { |
||||
| 425 | throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); |
||||
| 426 | } |
||||
| 427 | |||||
| 428 | $this->commands[$command->getName()] = $command; |
||||
| 429 | |||||
| 430 | foreach ($command->getAliases() as $alias) { |
||||
| 431 | $this->commands[$alias] = $command; |
||||
| 432 | } |
||||
| 433 | |||||
| 434 | return $command; |
||||
| 435 | } |
||||
| 436 | |||||
| 437 | /** |
||||
| 438 | * 获取指令 |
||||
| 439 | * @access public |
||||
| 440 | * @param string $name 指令名称 |
||||
| 441 | * @return Command |
||||
| 442 | * @throws InvalidArgumentException |
||||
| 443 | */ |
||||
| 444 | public function getCommand(string $name): Command |
||||
| 445 | { |
||||
| 446 | if (!isset($this->commands[$name])) { |
||||
| 447 | throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); |
||||
| 448 | } |
||||
| 449 | |||||
| 450 | $command = $this->commands[$name]; |
||||
| 451 | |||||
| 452 | if (is_string($command)) { |
||||
|
0 ignored issues
–
show
|
|||||
| 453 | $command = $this->app->invokeClass($command); |
||||
| 454 | /** @var Command $command */ |
||||
| 455 | $command->setConsole($this); |
||||
| 456 | $command->setApp($this->app); |
||||
| 457 | } |
||||
| 458 | |||||
| 459 | if ($this->wantHelps) { |
||||
| 460 | $this->wantHelps = false; |
||||
| 461 | |||||
| 462 | /** @var HelpCommand $helpCommand */ |
||||
| 463 | $helpCommand = $this->getCommand('help'); |
||||
| 464 | $helpCommand->setCommand($command); |
||||
| 465 | |||||
| 466 | return $helpCommand; |
||||
| 467 | } |
||||
| 468 | |||||
| 469 | return $command; |
||||
| 470 | } |
||||
| 471 | |||||
| 472 | /** |
||||
| 473 | * 某个指令是否存在 |
||||
| 474 | * @access public |
||||
| 475 | * @param string $name 指令名称 |
||||
| 476 | * @return bool |
||||
| 477 | */ |
||||
| 478 | public function hasCommand(string $name): bool |
||||
| 479 | { |
||||
| 480 | return isset($this->commands[$name]); |
||||
| 481 | } |
||||
| 482 | |||||
| 483 | /** |
||||
| 484 | * 获取所有的命名空间 |
||||
| 485 | * @access public |
||||
| 486 | * @return array |
||||
| 487 | */ |
||||
| 488 | public function getNamespaces(): array |
||||
| 489 | { |
||||
| 490 | $namespaces = []; |
||||
| 491 | foreach ($this->commands as $key => $command) { |
||||
| 492 | if (is_string($command)) { |
||||
| 493 | $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key)); |
||||
| 494 | } else { |
||||
| 495 | $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); |
||||
| 496 | |||||
| 497 | foreach ($command->getAliases() as $alias) { |
||||
| 498 | $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); |
||||
| 499 | } |
||||
| 500 | } |
||||
| 501 | } |
||||
| 502 | |||||
| 503 | return array_values(array_unique(array_filter($namespaces))); |
||||
| 504 | } |
||||
| 505 | |||||
| 506 | /** |
||||
| 507 | * 查找注册命名空间中的名称或缩写。 |
||||
| 508 | * @access public |
||||
| 509 | * @param string $namespace |
||||
| 510 | * @return string |
||||
| 511 | * @throws InvalidArgumentException |
||||
| 512 | */ |
||||
| 513 | public function findNamespace(string $namespace): string |
||||
| 514 | { |
||||
| 515 | $allNamespaces = $this->getNamespaces(); |
||||
| 516 | $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { |
||||
| 517 | return preg_quote($matches[1]) . '[^:]*'; |
||||
| 518 | }, $namespace); |
||||
| 519 | $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); |
||||
| 520 | |||||
| 521 | if (empty($namespaces)) { |
||||
| 522 | $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); |
||||
| 523 | |||||
| 524 | if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { |
||||
| 525 | if (1 == count($alternatives)) { |
||||
| 526 | $message .= "\n\nDid you mean this?\n "; |
||||
| 527 | } else { |
||||
| 528 | $message .= "\n\nDid you mean one of these?\n "; |
||||
| 529 | } |
||||
| 530 | |||||
| 531 | $message .= implode("\n ", $alternatives); |
||||
| 532 | } |
||||
| 533 | |||||
| 534 | throw new InvalidArgumentException($message); |
||||
| 535 | } |
||||
| 536 | |||||
| 537 | $exact = in_array($namespace, $namespaces, true); |
||||
| 538 | if (count($namespaces) > 1 && !$exact) { |
||||
| 539 | throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); |
||||
| 540 | } |
||||
| 541 | |||||
| 542 | return $exact ? $namespace : reset($namespaces); |
||||
| 543 | } |
||||
| 544 | |||||
| 545 | /** |
||||
| 546 | * 查找指令 |
||||
| 547 | * @access public |
||||
| 548 | * @param string $name 名称或者别名 |
||||
| 549 | * @return Command |
||||
| 550 | * @throws InvalidArgumentException |
||||
| 551 | */ |
||||
| 552 | public function find(string $name): Command |
||||
| 553 | { |
||||
| 554 | $allCommands = array_keys($this->commands); |
||||
| 555 | |||||
| 556 | $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { |
||||
| 557 | return preg_quote($matches[1]) . '[^:]*'; |
||||
| 558 | }, $name); |
||||
| 559 | |||||
| 560 | $commands = preg_grep('{^' . $expr . '}', $allCommands); |
||||
| 561 | |||||
| 562 | if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { |
||||
| 563 | if (false !== $pos = strrpos($name, ':')) { |
||||
| 564 | $this->findNamespace(substr($name, 0, $pos)); |
||||
| 565 | } |
||||
| 566 | |||||
| 567 | $message = sprintf('Command "%s" is not defined.', $name); |
||||
| 568 | |||||
| 569 | if ($alternatives = $this->findAlternatives($name, $allCommands)) { |
||||
| 570 | if (1 == count($alternatives)) { |
||||
| 571 | $message .= "\n\nDid you mean this?\n "; |
||||
| 572 | } else { |
||||
| 573 | $message .= "\n\nDid you mean one of these?\n "; |
||||
| 574 | } |
||||
| 575 | $message .= implode("\n ", $alternatives); |
||||
| 576 | } |
||||
| 577 | |||||
| 578 | throw new InvalidArgumentException($message); |
||||
| 579 | } |
||||
| 580 | |||||
| 581 | $exact = in_array($name, $commands, true); |
||||
| 582 | if (count($commands) > 1 && !$exact) { |
||||
| 583 | $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); |
||||
| 584 | |||||
| 585 | throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); |
||||
| 586 | } |
||||
| 587 | |||||
| 588 | return $this->getCommand($exact ? $name : reset($commands)); |
||||
| 589 | } |
||||
| 590 | |||||
| 591 | /** |
||||
| 592 | * 获取所有的指令 |
||||
| 593 | * @access public |
||||
| 594 | * @param string $namespace 命名空间 |
||||
| 595 | * @return Command[] |
||||
| 596 | * @api |
||||
| 597 | */ |
||||
| 598 | public function all(string $namespace = null): array |
||||
| 599 | { |
||||
| 600 | if (null === $namespace) { |
||||
| 601 | return $this->commands; |
||||
| 602 | } |
||||
| 603 | |||||
| 604 | $commands = []; |
||||
| 605 | foreach ($this->commands as $name => $command) { |
||||
| 606 | if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { |
||||
| 607 | $commands[$name] = $command; |
||||
| 608 | } |
||||
| 609 | } |
||||
| 610 | |||||
| 611 | return $commands; |
||||
| 612 | } |
||||
| 613 | |||||
| 614 | /** |
||||
| 615 | * 配置基于用户的参数和选项的输入和输出实例。 |
||||
| 616 | * @access protected |
||||
| 617 | * @param Input $input 输入实例 |
||||
| 618 | * @param Output $output 输出实例 |
||||
| 619 | */ |
||||
| 620 | protected function configureIO(Input $input, Output $output): void |
||||
| 621 | { |
||||
| 622 | if (true === $input->hasParameterOption(['--ansi'])) { |
||||
| 623 | $output->setDecorated(true); |
||||
| 624 | } elseif (true === $input->hasParameterOption(['--no-ansi'])) { |
||||
| 625 | $output->setDecorated(false); |
||||
| 626 | } |
||||
| 627 | |||||
| 628 | if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { |
||||
| 629 | $input->setInteractive(false); |
||||
| 630 | } |
||||
| 631 | |||||
| 632 | if (true === $input->hasParameterOption(['--quiet', '-q'])) { |
||||
| 633 | $output->setVerbosity(Output::VERBOSITY_QUIET); |
||||
| 634 | } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { |
||||
| 635 | $output->setVerbosity(Output::VERBOSITY_DEBUG); |
||||
| 636 | } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { |
||||
| 637 | $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); |
||||
| 638 | } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { |
||||
| 639 | $output->setVerbosity(Output::VERBOSITY_VERBOSE); |
||||
| 640 | } |
||||
| 641 | } |
||||
| 642 | |||||
| 643 | /** |
||||
| 644 | * 执行指令 |
||||
| 645 | * @access protected |
||||
| 646 | * @param Command $command 指令实例 |
||||
| 647 | * @param Input $input 输入实例 |
||||
| 648 | * @param Output $output 输出实例 |
||||
| 649 | * @return int |
||||
| 650 | * @throws \Exception |
||||
| 651 | */ |
||||
| 652 | protected function doRunCommand(Command $command, Input $input, Output $output) |
||||
| 653 | { |
||||
| 654 | return $command->run($input, $output); |
||||
| 655 | } |
||||
| 656 | |||||
| 657 | /** |
||||
| 658 | * 获取指令的基础名称 |
||||
| 659 | * @access protected |
||||
| 660 | * @param Input $input |
||||
| 661 | * @return string |
||||
| 662 | */ |
||||
| 663 | protected function getCommandName(Input $input): string |
||||
| 664 | { |
||||
| 665 | return $input->getFirstArgument() ?: ''; |
||||
| 666 | } |
||||
| 667 | |||||
| 668 | /** |
||||
| 669 | * 获取默认输入定义 |
||||
| 670 | * @access protected |
||||
| 671 | * @return InputDefinition |
||||
| 672 | */ |
||||
| 673 | protected function getDefaultInputDefinition(): InputDefinition |
||||
| 674 | { |
||||
| 675 | return new InputDefinition([ |
||||
| 676 | new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), |
||||
| 677 | new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), |
||||
| 678 | new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), |
||||
| 679 | new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), |
||||
| 680 | 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'), |
||||
| 681 | new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), |
||||
| 682 | new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), |
||||
| 683 | new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), |
||||
| 684 | ]); |
||||
| 685 | } |
||||
| 686 | |||||
| 687 | /** |
||||
| 688 | * 获取可能的建议 |
||||
| 689 | * @access private |
||||
| 690 | * @param array $abbrevs |
||||
| 691 | * @return string |
||||
| 692 | */ |
||||
| 693 | private function getAbbreviationSuggestions(array $abbrevs): string |
||||
| 694 | { |
||||
| 695 | return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); |
||||
| 696 | } |
||||
| 697 | |||||
| 698 | /** |
||||
| 699 | * 返回命名空间部分 |
||||
| 700 | * @access public |
||||
| 701 | * @param string $name 指令 |
||||
| 702 | * @param int $limit 部分的命名空间的最大数量 |
||||
| 703 | * @return string |
||||
| 704 | */ |
||||
| 705 | public function extractNamespace(string $name, int $limit = 0): string |
||||
| 706 | { |
||||
| 707 | $parts = explode(':', $name); |
||||
| 708 | array_pop($parts); |
||||
| 709 | |||||
| 710 | return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit)); |
||||
| 711 | } |
||||
| 712 | |||||
| 713 | /** |
||||
| 714 | * 查找可替代的建议 |
||||
| 715 | * @access private |
||||
| 716 | * @param string $name |
||||
| 717 | * @param array|\Traversable $collection |
||||
| 718 | * @return array |
||||
| 719 | */ |
||||
| 720 | private function findAlternatives(string $name, $collection): array |
||||
| 721 | { |
||||
| 722 | $threshold = 1e3; |
||||
| 723 | $alternatives = []; |
||||
| 724 | |||||
| 725 | $collectionParts = []; |
||||
| 726 | foreach ($collection as $item) { |
||||
| 727 | $collectionParts[$item] = explode(':', $item); |
||||
| 728 | } |
||||
| 729 | |||||
| 730 | foreach (explode(':', $name) as $i => $subname) { |
||||
| 731 | foreach ($collectionParts as $collectionName => $parts) { |
||||
| 732 | $exists = isset($alternatives[$collectionName]); |
||||
| 733 | if (!isset($parts[$i]) && $exists) { |
||||
| 734 | $alternatives[$collectionName] += $threshold; |
||||
| 735 | continue; |
||||
| 736 | } elseif (!isset($parts[$i])) { |
||||
| 737 | continue; |
||||
| 738 | } |
||||
| 739 | |||||
| 740 | $lev = levenshtein($subname, $parts[$i]); |
||||
| 741 | if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { |
||||
| 742 | $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; |
||||
| 743 | } elseif ($exists) { |
||||
| 744 | $alternatives[$collectionName] += $threshold; |
||||
| 745 | } |
||||
| 746 | } |
||||
| 747 | } |
||||
| 748 | |||||
| 749 | foreach ($collection as $item) { |
||||
| 750 | $lev = levenshtein($name, $item); |
||||
| 751 | if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { |
||||
| 752 | $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; |
||||
| 753 | } |
||||
| 754 | } |
||||
| 755 | |||||
| 756 | $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { |
||||
| 757 | return $lev < 2 * $threshold; |
||||
| 758 | }); |
||||
| 759 | asort($alternatives); |
||||
| 760 | |||||
| 761 | return array_keys($alternatives); |
||||
| 762 | } |
||||
| 763 | |||||
| 764 | /** |
||||
| 765 | * 返回所有的命名空间 |
||||
| 766 | * @access private |
||||
| 767 | * @param string $name |
||||
| 768 | * @return array |
||||
| 769 | */ |
||||
| 770 | private function extractAllNamespaces(string $name): array |
||||
| 771 | { |
||||
| 772 | $parts = explode(':', $name, -1); |
||||
| 773 | $namespaces = []; |
||||
| 774 | |||||
| 775 | foreach ($parts as $part) { |
||||
| 776 | if (count($namespaces)) { |
||||
| 777 | $namespaces[] = end($namespaces) . ':' . $part; |
||||
| 778 | } else { |
||||
| 779 | $namespaces[] = $part; |
||||
| 780 | } |
||||
| 781 | } |
||||
| 782 | |||||
| 783 | return $namespaces; |
||||
| 784 | } |
||||
| 785 | |||||
| 786 | } |
||||
| 787 |
Let?s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let?s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare 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.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/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: