*/ abstract class BaseCommand implements CommandInterface, EventDispatcherInterface { /** * @use \Cake\Event\EventDispatcherTrait<\Cake\Command\Command> */ use EventDispatcherTrait; /** * The name of this command. * * @var string */ protected string $name = 'cake unknown'; protected ?CommandFactoryInterface $factory = null; /** * Constructor * * @param \Cake\Console\CommandFactoryInterface $factory Command factory instance. */ public function __construct(?CommandFactoryInterface $factory = null) { $this->factory = $factory; } /** * @inheritDoc */ public function setName(string $name) { assert( str_contains($name, ' ') && !str_starts_with($name, ' '), "The name '{$name}' is missing a space. Names should look like `cake routes`" ); $this->name = $name; return $this; } /** * Get the command name. * * @return string */ public function getName(): string { return $this->name; } /** * Get the command description. * * @return string */ public static function getDescription(): string { return ''; } /** * Get the root command name. * * @return string */ public function getRootName(): string { [$root] = explode(' ', $this->name); return $root; } /** * Get the command name. * * Returns the command name based on class name. * For e.g. for a command with class name `UpdateTableCommand` the default * name returned would be `'update_table'`. * * @return string */ public static function defaultName(): string { $pos = strrpos(static::class, '\\'); /** @psalm-suppress PossiblyFalseOperand */ $name = substr(static::class, $pos + 1, -7); return Inflector::underscore($name); } /** * Get the option parser. * * You can override buildOptionParser() to define your options & arguments. * * @return \Cake\Console\ConsoleOptionParser * @throws \Cake\Core\Exception\CakeException When the parser is invalid */ public function getOptionParser(): ConsoleOptionParser { [$root, $name] = explode(' ', $this->name, 2); $parser = new ConsoleOptionParser($name); $parser->setRootName($root); $parser->setDescription(static::getDescription()); $parser = $this->buildOptionParser($parser); return $parser; } /** * Hook method for defining this command's option parser. * * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { return $parser; } /** * Hook method invoked by CakePHP when a command is about to be executed. * * Override this method and implement expensive/important setup steps that * should not run on every command run. This method will be called *before* * the options and arguments are validated and processed. * * @return void */ public function initialize(): void { } /** * @inheritDoc */ public function run(array $argv, ConsoleIo $io): ?int { $this->initialize(); $parser = $this->getOptionParser(); try { [$options, $arguments] = $parser->parse($argv, $io); $args = new Arguments( $arguments, $options, $parser->argumentNames() ); } catch (ConsoleException $e) { $io->err('Error: ' . $e->getMessage()); return static::CODE_ERROR; } $this->setOutputLevel($args, $io); if ($args->getOption('help')) { $this->displayHelp($parser, $args, $io); return static::CODE_SUCCESS; } if ($args->getOption('quiet')) { $io->setInteractive(false); } $this->dispatchEvent('Command.beforeExecute', ['args' => $args]); /** @var int|null $result */ $result = $this->execute($args, $io); $this->dispatchEvent('Command.afterExecute', ['args' => $args, 'result' => $result]); return $result; } /** * Output help content * * @param \Cake\Console\ConsoleOptionParser $parser The option parser. * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return void */ protected function displayHelp(ConsoleOptionParser $parser, Arguments $args, ConsoleIo $io): void { $format = 'text'; if ($args->getArgumentAt(0) === 'xml') { $format = 'xml'; $io->setOutputAs(ConsoleOutput::RAW); } $io->out($parser->help($format)); } /** * Set the output level based on the Arguments. * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return void */ protected function setOutputLevel(Arguments $args, ConsoleIo $io): void { $io->setLoggers(ConsoleIo::NORMAL); if ($args->getOption('quiet')) { $io->level(ConsoleIo::QUIET); $io->setLoggers(ConsoleIo::QUIET); } if ($args->getOption('verbose')) { $io->level(ConsoleIo::VERBOSE); $io->setLoggers(ConsoleIo::VERBOSE); } } /** * Implement this method with your command's logic. * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return int|null|void The exit code or null for success * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ abstract public function execute(Arguments $args, ConsoleIo $io); /** * Halt the current process with a StopException. * * @param int $code The exit code to use. * @throws \Cake\Console\Exception\StopException * @return never */ public function abort(int $code = self::CODE_ERROR): never { throw new StopException('Command aborted', $code); } /** * Execute another command with the provided set of arguments. * * If you are using a string command name, that command's dependencies * will not be resolved with the application container. Instead you will * need to pass the command as an object with all of its dependencies. * * @param \Cake\Console\CommandInterface|string $command The command class name or command instance. * @param array $args The arguments to invoke the command with. * @param \Cake\Console\ConsoleIo|null $io The ConsoleIo instance to use for the executed command. * @return int|null The exit code or null for success of the command. */ public function executeCommand(CommandInterface|string $command, array $args = [], ?ConsoleIo $io = null): ?int { if (is_string($command)) { assert( is_subclass_of($command, CommandInterface::class), sprintf('Command `%s` is not a subclass of `%s`.', $command, CommandInterface::class) ); $command = $this->factory?->create($command) ?? new $command(); } $io = $io ?: new ConsoleIo(); try { return $command->run($args, $io); } catch (StopException $e) { return $e->getCode(); } } }