1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.6.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Console;
16:
17: use Cake\Console\Exception\ConsoleException;
18: use Cake\Console\Exception\StopException;
19: use Cake\Datasource\ModelAwareTrait;
20: use Cake\Log\LogTrait;
21: use Cake\ORM\Locator\LocatorAwareTrait;
22: use InvalidArgumentException;
23: use RuntimeException;
24:
25: /**
26: * Base class for console commands.
27: */
28: class Command
29: {
30: use LocatorAwareTrait;
31: use LogTrait;
32: use ModelAwareTrait;
33:
34: /**
35: * Default error code
36: *
37: * @var int
38: */
39: const CODE_ERROR = 1;
40:
41: /**
42: * Default success code
43: *
44: * @var int
45: */
46: const CODE_SUCCESS = 0;
47:
48: /**
49: * The name of this command.
50: *
51: * @var string
52: */
53: protected $name = 'cake unknown';
54:
55: /**
56: * Constructor
57: *
58: * By default CakePHP will construct command objects when
59: * building the CommandCollection for your application.
60: */
61: public function __construct()
62: {
63: $this->modelFactory('Table', function ($alias) {
64: return $this->getTableLocator()->get($alias);
65: });
66:
67: if (isset($this->modelClass)) {
68: $this->loadModel();
69: }
70: }
71:
72: /**
73: * Set the name this command uses in the collection.
74: *
75: * Generally invoked by the CommandCollection when the command is added.
76: * Required to have at least one space in the name so that the root
77: * command can be calculated.
78: *
79: * @param string $name The name the command uses in the collection.
80: * @return $this
81: * @throws \InvalidArgumentException
82: */
83: public function setName($name)
84: {
85: if (strpos($name, ' ') < 1) {
86: throw new InvalidArgumentException(
87: "The name '{$name}' is missing a space. Names should look like `cake routes`"
88: );
89: }
90: $this->name = $name;
91:
92: return $this;
93: }
94:
95: /**
96: * Get the command name.
97: *
98: * @return string
99: */
100: public function getName()
101: {
102: return $this->name;
103: }
104:
105: /**
106: * Get the option parser.
107: *
108: * You can override buildOptionParser() to define your options & arguments.
109: *
110: * @return \Cake\Console\ConsoleOptionParser
111: * @throws \RuntimeException When the parser is invalid
112: */
113: public function getOptionParser()
114: {
115: list($root, $name) = explode(' ', $this->name, 2);
116: $parser = new ConsoleOptionParser($name);
117: $parser->setRootName($root);
118:
119: $parser = $this->buildOptionParser($parser);
120: if (!($parser instanceof ConsoleOptionParser)) {
121: throw new RuntimeException(sprintf(
122: "Invalid option parser returned from buildOptionParser(). Expected %s, got %s",
123: ConsoleOptionParser::class,
124: getTypeName($parser)
125: ));
126: }
127:
128: return $parser;
129: }
130:
131: /**
132: * Hook method for defining this command's option parser.
133: *
134: * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined
135: * @return \Cake\Console\ConsoleOptionParser The built parser.
136: */
137: protected function buildOptionParser(ConsoleOptionParser $parser)
138: {
139: return $parser;
140: }
141:
142: /**
143: * Hook method invoked by CakePHP when a command is about to be executed.
144: *
145: * Override this method and implement expensive/important setup steps that
146: * should not run on every command run. This method will be called *before*
147: * the options and arguments are validated and processed.
148: *
149: * @return void
150: */
151: public function initialize()
152: {
153: }
154:
155: /**
156: * Run the command.
157: *
158: * @param array $argv Arguments from the CLI environment.
159: * @param \Cake\Console\ConsoleIo $io The console io
160: * @return int|null Exit code or null for success.
161: */
162: public function run(array $argv, ConsoleIo $io)
163: {
164: $this->initialize();
165:
166: $parser = $this->getOptionParser();
167: try {
168: list($options, $arguments) = $parser->parse($argv);
169: $args = new Arguments(
170: $arguments,
171: $options,
172: $parser->argumentNames()
173: );
174: } catch (ConsoleException $e) {
175: $io->err('Error: ' . $e->getMessage());
176:
177: return static::CODE_ERROR;
178: }
179: $this->setOutputLevel($args, $io);
180:
181: if ($args->getOption('help')) {
182: $this->displayHelp($parser, $args, $io);
183:
184: return static::CODE_SUCCESS;
185: }
186:
187: return $this->execute($args, $io);
188: }
189:
190: /**
191: * Output help content
192: *
193: * @param \Cake\Console\ConsoleOptionParser $parser The option parser.
194: * @param \Cake\Console\Arguments $args The command arguments.
195: * @param \Cake\Console\ConsoleIo $io The console io
196: * @return void
197: */
198: protected function displayHelp(ConsoleOptionParser $parser, Arguments $args, ConsoleIo $io)
199: {
200: $format = 'text';
201: if ($args->getArgumentAt(0) === 'xml') {
202: $format = 'xml';
203: $io->setOutputAs(ConsoleOutput::RAW);
204: }
205:
206: $io->out($parser->help(null, $format));
207: }
208:
209: /**
210: * Set the output level based on the Arguments.
211: *
212: * @param \Cake\Console\Arguments $args The command arguments.
213: * @param \Cake\Console\ConsoleIo $io The console io
214: * @return void
215: */
216: protected function setOutputLevel(Arguments $args, ConsoleIo $io)
217: {
218: $io->setLoggers(ConsoleIo::NORMAL);
219: if ($args->getOption('quiet')) {
220: $io->level(ConsoleIo::QUIET);
221: $io->setLoggers(ConsoleIo::QUIET);
222: }
223: if ($args->getOption('verbose')) {
224: $io->level(ConsoleIo::VERBOSE);
225: $io->setLoggers(ConsoleIo::VERBOSE);
226: }
227: }
228:
229: /**
230: * Implement this method with your command's logic.
231: *
232: * @param \Cake\Console\Arguments $args The command arguments.
233: * @param \Cake\Console\ConsoleIo $io The console io
234: * @return int|null The exit code or null for success
235: */
236: public function execute(Arguments $args, ConsoleIo $io)
237: {
238: return null;
239: }
240:
241: /**
242: * Halt the the current process with a StopException.
243: *
244: * @param int $code The exit code to use.
245: * @throws \Cake\Console\Exception\StopException
246: * @return void
247: */
248: public function abort($code = self::CODE_ERROR)
249: {
250: throw new StopException('Command aborted', $code);
251: }
252:
253: /**
254: * Execute another command with the provided set of arguments.
255: *
256: * @param string|\Cake\Console\Command $command The command class name or command instance.
257: * @param array $args The arguments to invoke the command with.
258: * @param \Cake\Console\ConsoleIo $io The ConsoleIo instance to use for the executed command.
259: * @return int|null The exit code or null for success of the command.
260: */
261: public function executeCommand($command, array $args = [], ConsoleIo $io = null)
262: {
263: if (is_string($command)) {
264: if (!class_exists($command)) {
265: throw new InvalidArgumentException("Command class '{$command}' does not exist.");
266: }
267: $command = new $command();
268: }
269: if (!$command instanceof Command) {
270: $commandType = getTypeName($command);
271: throw new InvalidArgumentException(
272: "Command '{$commandType}' is not a subclass of Cake\Console\Command."
273: );
274: }
275: $io = $io ?: new ConsoleIo();
276:
277: try {
278: return $command->run($args, $io);
279: } catch (StopException $e) {
280: return $e->getCode();
281: }
282: }
283: }
284: