-
Notifications
You must be signed in to change notification settings - Fork 2
/
BaseCommand.php
276 lines (247 loc) · 8.08 KB
/
BaseCommand.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 4.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Console;
use Cake\Console\Exception\ConsoleException;
use Cake\Console\Exception\StopException;
use Cake\Utility\Inflector;
use InvalidArgumentException;
use RuntimeException;
/**
* Base class for console commands.
*
* Provides hooks for common command features:
*
* - `initialize` Acts as a post-construct hook.
* - `buildOptionParser` Build/Configure the option parser for your command.
* - `execute` Execute your command with parsed Arguments and ConsoleIo
*/
abstract class BaseCommand implements CommandInterface
{
/**
* The name of this command.
*
* @var string
*/
protected $name = 'cake unknown';
/**
* @inheritDoc
*/
public function setName(string $name)
{
if (strpos($name, ' ') < 1) {
throw new InvalidArgumentException(
"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 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);
$name = Inflector::underscore($name);
return $name;
}
/**
* Get the option parser.
*
* You can override buildOptionParser() to define your options & arguments.
*
* @return \Cake\Console\ConsoleOptionParser
* @throws \RuntimeException When the parser is invalid
*/
public function getOptionParser(): ConsoleOptionParser
{
[$root, $name] = explode(' ', $this->name, 2);
$parser = new ConsoleOptionParser($name);
$parser->setRootName($root);
$parser = $this->buildOptionParser($parser);
if ($parser->subcommands()) {
throw new RuntimeException(
'You cannot add sub-commands to `Command` sub-classes. Instead make a separate command.'
);
}
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);
$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);
}
return $this->execute($args, $io);
}
/**
* 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(null, $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
*/
abstract public function execute(Arguments $args, ConsoleIo $io);
/**
* Halt the the current process with a StopException.
*
* @param int $code The exit code to use.
* @throws \Cake\Console\Exception\StopException
* @return void
*/
public function abort(int $code = self::CODE_ERROR): void
{
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 string|\Cake\Console\CommandInterface $command The command class name or command instance.
* @param array $args The arguments to invoke the command with.
* @param \Cake\Console\ConsoleIo $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($command, array $args = [], ?ConsoleIo $io = null): ?int
{
if (is_string($command)) {
if (!class_exists($command)) {
throw new InvalidArgumentException("Command class '{$command}' does not exist.");
}
$command = new $command();
}
if (!$command instanceof CommandInterface) {
$commandType = getTypeName($command);
throw new InvalidArgumentException(
"Command '{$commandType}' is not a subclass of Cake\Console\CommandInterface."
);
}
$io = $io ?: new ConsoleIo();
try {
return $command->run($args, $io);
} catch (StopException $e) {
return $e->getCode();
}
}
}