Command.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Command;
  12. use Hyperf\Coroutine\Coroutine;
  13. use Psr\EventDispatcher\EventDispatcherInterface;
  14. use Swoole\ExitException;
  15. use Symfony\Component\Console\Command\Command as SymfonyCommand;
  16. use Symfony\Component\Console\Input\ArrayInput;
  17. use Symfony\Component\Console\Input\InputInterface;
  18. use Symfony\Component\Console\Output\OutputInterface;
  19. use Symfony\Component\Console\Style\SymfonyStyle;
  20. use Throwable;
  21. use function Hyperf\Collection\collect;
  22. use function Hyperf\Coroutine\run;
  23. use function Hyperf\Support\class_basename;
  24. use function Hyperf\Support\class_uses_recursive;
  25. use function Hyperf\Support\swoole_hook_flags;
  26. use function Hyperf\Tappable\tap;
  27. abstract class Command extends SymfonyCommand
  28. {
  29. use Concerns\DisableEventDispatcher;
  30. use Concerns\HasParameters;
  31. use Concerns\InteractsWithIO;
  32. /**
  33. * The name of the command.
  34. */
  35. protected ?string $name = null;
  36. /**
  37. * The description of the command.
  38. */
  39. protected string $description = '';
  40. /**
  41. * Execution in a coroutine environment.
  42. */
  43. protected bool $coroutine = true;
  44. /**
  45. * The eventDispatcher.
  46. */
  47. protected ?EventDispatcherInterface $eventDispatcher = null;
  48. /**
  49. * The hookFlags of the command.
  50. */
  51. protected int $hookFlags = -1;
  52. /**
  53. * The name and signature of the command.
  54. */
  55. protected ?string $signature = null;
  56. /**
  57. * The exit code of the command.
  58. */
  59. protected int $exitCode = self::SUCCESS;
  60. public function __construct(?string $name = null)
  61. {
  62. $this->name = $name ?? $this->name;
  63. if ($this->hookFlags < 0) {
  64. $this->hookFlags = swoole_hook_flags();
  65. }
  66. if (isset($this->signature)) {
  67. $this->configureUsingFluentDefinition();
  68. } else {
  69. parent::__construct($this->name);
  70. }
  71. $this->addDisableDispatcherOption();
  72. if (! empty($this->description)) {
  73. $this->setDescription($this->description);
  74. }
  75. if (! isset($this->signature)) {
  76. $this->specifyParameters();
  77. }
  78. }
  79. /**
  80. * Run the console command.
  81. */
  82. public function run(InputInterface $input, OutputInterface $output): int
  83. {
  84. $this->output = new SymfonyStyle($input, $output);
  85. $this->setUpTraits($this->input = $input, $this->output);
  86. return parent::run($this->input, $this->output);
  87. }
  88. /**
  89. * Call another console command.
  90. */
  91. public function call(string $command, array $arguments = []): int
  92. {
  93. $arguments['command'] = $command;
  94. return $this->getApplication()->find($command)->run($this->createInputFromArguments($arguments), $this->output);
  95. }
  96. /**
  97. * Create an input instance from the given arguments.
  98. */
  99. protected function createInputFromArguments(array $arguments): ArrayInput
  100. {
  101. return tap(new ArrayInput(array_merge($this->context(), $arguments)), function (InputInterface $input) {
  102. if ($input->hasParameterOption(['--no-interaction'], true)) {
  103. $input->setInteractive(false);
  104. }
  105. });
  106. }
  107. /**
  108. * Get all the context passed to the command.
  109. */
  110. protected function context(): array
  111. {
  112. return collect($this->input->getOptions())->only([
  113. 'ansi',
  114. 'no-ansi',
  115. 'no-interaction',
  116. 'quiet',
  117. 'verbose',
  118. ])->filter()->mapWithKeys(function ($value, $key) {
  119. return ["--{$key}" => $value];
  120. })->all();
  121. }
  122. /**
  123. * Configure the console command using a fluent definition.
  124. */
  125. protected function configureUsingFluentDefinition()
  126. {
  127. [$name, $arguments, $options] = Parser::parse($this->signature);
  128. parent::__construct($this->name = $name);
  129. // After parsing the signature we will spin through the arguments and options
  130. // and set them on this command. These will already be changed into proper
  131. // instances of these "InputArgument" and "InputOption" Symfony classes.
  132. $this->getDefinition()->addArguments($arguments);
  133. $this->getDefinition()->addOptions($options);
  134. }
  135. protected function configure()
  136. {
  137. parent::configure();
  138. }
  139. protected function execute(InputInterface $input, OutputInterface $output): int
  140. {
  141. $this->disableDispatcher($input);
  142. $method = method_exists($this, 'handle') ? 'handle' : '__invoke';
  143. $callback = function () use ($method): int {
  144. try {
  145. $this->eventDispatcher?->dispatch(new Event\BeforeHandle($this));
  146. $statusCode = $this->{$method}();
  147. if (is_int($statusCode)) {
  148. $this->exitCode = $statusCode;
  149. }
  150. $this->eventDispatcher?->dispatch(new Event\AfterHandle($this));
  151. } catch (Throwable $exception) {
  152. if (class_exists(ExitException::class) && $exception instanceof ExitException) {
  153. return $this->exitCode = (int) $exception->getStatus();
  154. }
  155. if (! $this->eventDispatcher) {
  156. throw $exception;
  157. }
  158. $this->getApplication()?->renderThrowable($exception, $this->output);
  159. $this->exitCode = self::FAILURE;
  160. $this->eventDispatcher->dispatch(new Event\FailToHandle($this, $exception));
  161. } finally {
  162. $this->eventDispatcher?->dispatch(new Event\AfterExecute($this, $exception ?? null));
  163. }
  164. return $this->exitCode;
  165. };
  166. if ($this->coroutine && ! Coroutine::inCoroutine()) {
  167. run($callback, $this->hookFlags);
  168. } else {
  169. $callback();
  170. }
  171. return $this->exitCode >= 0 && $this->exitCode <= 255 ? $this->exitCode : self::INVALID;
  172. }
  173. /**
  174. * Setup traits of command.
  175. */
  176. protected function setUpTraits(InputInterface $input, OutputInterface $output): array
  177. {
  178. $uses = array_flip(class_uses_recursive(static::class));
  179. foreach ($uses as $trait) {
  180. if (method_exists($this, $method = 'setUp' . class_basename($trait))) {
  181. $this->{$method}($input, $output);
  182. }
  183. }
  184. return $uses;
  185. }
  186. }