1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) |
4
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org) |
11
|
|
|
* @link http://cakephp.org CakePHP(tm) Project |
12
|
|
|
* @since 3.5.0 |
13
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
14
|
|
|
*/ |
15
|
|
|
namespace Cake\Console; |
16
|
|
|
|
17
|
|
|
use Cake\Command\HelpCommand; |
18
|
|
|
use Cake\Command\VersionCommand; |
19
|
|
|
use Cake\Console\Exception\StopException; |
20
|
|
|
use Cake\Core\ConsoleApplicationInterface; |
21
|
|
|
use Cake\Core\HttpApplicationInterface; |
22
|
|
|
use Cake\Core\PluginApplicationInterface; |
23
|
|
|
use Cake\Event\EventDispatcherInterface; |
24
|
|
|
use Cake\Event\EventDispatcherTrait; |
25
|
|
|
use Cake\Event\EventManager; |
26
|
|
|
use Cake\Routing\Router; |
27
|
|
|
use Cake\Utility\Inflector; |
28
|
|
|
use InvalidArgumentException; |
29
|
|
|
use RuntimeException; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Run CLI commands for the provided application. |
33
|
|
|
*/ |
34
|
|
|
class CommandRunner implements EventDispatcherInterface |
35
|
|
|
{ |
36
|
|
|
/** |
37
|
|
|
* Alias methods away so we can implement proxying methods. |
38
|
|
|
*/ |
39
|
|
|
use EventDispatcherTrait { |
40
|
|
|
eventManager as private _eventManager; |
41
|
|
|
getEventManager as private _getEventManager; |
42
|
|
|
setEventManager as private _setEventManager; |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* The application console commands are being run for. |
47
|
|
|
* |
48
|
|
|
* @var \Cake\Core\ConsoleApplicationInterface |
49
|
|
|
*/ |
50
|
|
|
protected $app; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* The application console commands are being run for. |
54
|
|
|
* |
55
|
|
|
* @var \Cake\Console\CommandFactoryInterface |
56
|
|
|
*/ |
57
|
|
|
protected $factory; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The root command name. Defaults to `cake`. |
61
|
|
|
* |
62
|
|
|
* @var string |
63
|
|
|
*/ |
64
|
|
|
protected $root; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Alias mappings. |
68
|
|
|
* |
69
|
|
|
* @var array |
70
|
|
|
*/ |
71
|
|
|
protected $aliases = []; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Constructor |
75
|
|
|
* |
76
|
|
|
* @param \Cake\Core\ConsoleApplicationInterface $app The application to run CLI commands for. |
77
|
|
|
* @param string $root The root command name to be removed from argv. |
78
|
|
|
* @param \Cake\Console\CommandFactoryInterface|null $factory Command factory instance. |
79
|
|
|
*/ |
80
|
|
|
public function __construct(ConsoleApplicationInterface $app, $root = 'cake', CommandFactoryInterface $factory = null) |
81
|
|
|
{ |
82
|
|
|
$this->app = $app; |
83
|
|
|
$this->root = $root; |
84
|
|
|
$this->factory = $factory ?: new CommandFactory(); |
85
|
|
|
$this->aliases = [ |
86
|
|
|
'--version' => 'version', |
87
|
|
|
'--help' => 'help', |
88
|
|
|
'-h' => 'help', |
89
|
|
|
]; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Replace the entire alias map for a runner. |
94
|
|
|
* |
95
|
|
|
* Aliases allow you to define alternate names for commands |
96
|
|
|
* in the collection. This can be useful to add top level switches |
97
|
|
|
* like `--version` or `-h` |
98
|
|
|
* |
99
|
|
|
* ### Usage |
100
|
|
|
* |
101
|
|
|
* ``` |
102
|
|
|
* $runner->setAliases(['--version' => 'version']); |
103
|
|
|
* ``` |
104
|
|
|
* |
105
|
|
|
* @param array $aliases The map of aliases to replace. |
106
|
|
|
* @return $this |
107
|
|
|
*/ |
108
|
|
|
public function setAliases(array $aliases) |
109
|
|
|
{ |
110
|
|
|
$this->aliases = $aliases; |
111
|
|
|
|
112
|
|
|
return $this; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Run the command contained in $argv. |
117
|
|
|
* |
118
|
|
|
* Use the application to do the following: |
119
|
|
|
* |
120
|
|
|
* - Bootstrap the application |
121
|
|
|
* - Create the CommandCollection using the console() hook on the application. |
122
|
|
|
* - Trigger the `Console.buildCommands` event of auto-wiring plugins. |
123
|
|
|
* - Run the requested command. |
124
|
|
|
* |
125
|
|
|
* @param array $argv The arguments from the CLI environment. |
126
|
|
|
* @param \Cake\Console\ConsoleIo $io The ConsoleIo instance. Used primarily for testing. |
127
|
|
|
* @return int The exit code of the command. |
128
|
|
|
* @throws \RuntimeException |
129
|
|
|
*/ |
130
|
|
|
public function run(array $argv, ConsoleIo $io = null) |
131
|
|
|
{ |
132
|
|
|
$this->bootstrap(); |
133
|
|
|
|
134
|
|
|
$commands = new CommandCollection([ |
135
|
|
|
'version' => VersionCommand::class, |
136
|
|
|
'help' => HelpCommand::class, |
137
|
|
|
]); |
138
|
|
|
$commands = $this->app->console($commands); |
139
|
|
|
$this->checkCollection($commands, 'console'); |
|
|
|
|
140
|
|
|
|
141
|
|
|
if ($this->app instanceof PluginApplicationInterface) { |
142
|
|
|
$commands = $this->app->pluginConsole($commands); |
143
|
|
|
} |
144
|
|
|
$this->checkCollection($commands, 'pluginConsole'); |
|
|
|
|
145
|
|
|
$this->dispatchEvent('Console.buildCommands', ['commands' => $commands]); |
146
|
|
|
$this->loadRoutes(); |
147
|
|
|
|
148
|
|
|
if (empty($argv)) { |
149
|
|
|
throw new RuntimeException("Cannot run any commands. No arguments received."); |
150
|
|
|
} |
151
|
|
|
// Remove the root executable segment |
152
|
|
|
array_shift($argv); |
153
|
|
|
|
154
|
|
|
$io = $io ?: new ConsoleIo(); |
155
|
|
|
|
156
|
|
|
list($name, $argv) = $this->longestCommandName($commands, $argv); |
157
|
|
|
$name = $this->resolveName($commands, $io, $name); |
158
|
|
|
|
159
|
|
|
$result = Command::CODE_ERROR; |
160
|
|
|
$shell = $this->getShell($io, $commands, $name); |
161
|
|
|
if ($shell instanceof Shell) { |
162
|
|
|
$result = $this->runShell($shell, $argv); |
163
|
|
|
} |
164
|
|
|
if ($shell instanceof Command) { |
165
|
|
|
$result = $this->runCommand($shell, $argv, $io); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
if ($result === null || $result === true) { |
169
|
|
|
return Command::CODE_SUCCESS; |
170
|
|
|
} |
171
|
|
|
if (is_int($result)) { |
172
|
|
|
return $result; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
return Command::CODE_ERROR; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Application bootstrap wrapper. |
180
|
|
|
* |
181
|
|
|
* Calls `bootstrap()` and `events()` if application implements `EventApplicationInterface`. |
182
|
|
|
* After the application is bootstrapped and events are attached, plugins are bootstrapped |
183
|
|
|
* and have their events attached. |
184
|
|
|
* |
185
|
|
|
* @return void |
186
|
|
|
*/ |
187
|
|
|
protected function bootstrap() |
188
|
|
|
{ |
189
|
|
|
$this->app->bootstrap(); |
190
|
|
|
if ($this->app instanceof PluginApplicationInterface) { |
191
|
|
|
$this->app->pluginBootstrap(); |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Check the created CommandCollection |
197
|
|
|
* |
198
|
|
|
* @param mixed $commands The CommandCollection to check, could be anything though. |
199
|
|
|
* @param string $method The method that was used. |
200
|
|
|
* @return void |
201
|
|
|
* @throws \RuntimeException |
202
|
|
|
* @deprecated 3.6.0 This method should be replaced with return types in 4.x |
203
|
|
|
*/ |
204
|
|
|
protected function checkCollection($commands, $method) |
205
|
|
|
{ |
206
|
|
|
if (!($commands instanceof CommandCollection)) { |
207
|
|
|
$type = getTypeName($commands); |
208
|
|
|
throw new RuntimeException( |
209
|
|
|
"The application's `{$method}` method did not return a CommandCollection." . |
210
|
|
|
" Got '{$type}' instead." |
211
|
|
|
); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Get the application's event manager or the global one. |
217
|
|
|
* |
218
|
|
|
* @return \Cake\Event\EventManagerInterface |
219
|
|
|
*/ |
220
|
|
|
public function getEventManager() |
221
|
|
|
{ |
222
|
|
|
if ($this->app instanceof PluginApplicationInterface) { |
223
|
|
|
return $this->app->getEventManager(); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
return EventManager::instance(); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Get/set the application's event manager. |
231
|
|
|
* |
232
|
|
|
* If the application does not support events and this method is used as |
233
|
|
|
* a setter, an exception will be raised. |
234
|
|
|
* |
235
|
|
|
* @param \Cake\Event\EventManager|null $events The event manager to set. |
236
|
|
|
* @return \Cake\Event\EventManager|$this |
237
|
|
|
* @deprecated 3.6.0 Will be removed in 4.0 |
238
|
|
|
*/ |
239
|
|
View Code Duplication |
public function eventManager(EventManager $events = null) |
240
|
|
|
{ |
241
|
|
|
deprecationWarning('eventManager() is deprecated. Use getEventManager()/setEventManager() instead.'); |
242
|
|
|
if ($events === null) { |
243
|
|
|
return $this->getEventManager(); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
return $this->setEventManager($events); |
|
|
|
|
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Get/set the application's event manager. |
251
|
|
|
* |
252
|
|
|
* If the application does not support events and this method is used as |
253
|
|
|
* a setter, an exception will be raised. |
254
|
|
|
* |
255
|
|
|
* @param \Cake\Event\EventManager $events The event manager to set. |
256
|
|
|
* @return $this |
257
|
|
|
*/ |
258
|
|
View Code Duplication |
public function setEventManager(EventManager $events) |
259
|
|
|
{ |
260
|
|
|
if ($this->app instanceof PluginApplicationInterface) { |
261
|
|
|
$this->app->setEventManager($events); |
262
|
|
|
|
263
|
|
|
return $this; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
throw new InvalidArgumentException('Cannot set the event manager, the application does not support events.'); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Get the shell instance for a given command name |
271
|
|
|
* |
272
|
|
|
* @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class. |
273
|
|
|
* @param \Cake\Console\CommandCollection $commands The command collection to find the shell in. |
274
|
|
|
* @param string $name The command name to find |
275
|
|
|
* @return \Cake\Console\Shell|\Cake\Console\Command |
276
|
|
|
*/ |
277
|
|
|
protected function getShell(ConsoleIo $io, CommandCollection $commands, $name) |
278
|
|
|
{ |
279
|
|
|
$instance = $commands->get($name); |
280
|
|
|
if (is_string($instance)) { |
281
|
|
|
$instance = $this->createShell($instance, $io); |
282
|
|
|
} |
283
|
|
|
if ($instance instanceof Shell) { |
284
|
|
|
$instance->setRootName($this->root); |
285
|
|
|
} |
286
|
|
|
if ($instance instanceof Command) { |
287
|
|
|
$instance->setName("{$this->root} {$name}"); |
288
|
|
|
} |
289
|
|
|
if ($instance instanceof CommandCollectionAwareInterface) { |
290
|
|
|
$instance->setCommandCollection($commands); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
return $instance; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Build the longest command name that exists in the collection |
298
|
|
|
* |
299
|
|
|
* Build the longest command name that matches a |
300
|
|
|
* defined command. This will traverse a maximum of 3 tokens. |
301
|
|
|
* |
302
|
|
|
* @param \Cake\Console\CommandCollection $commands The command collection to check. |
303
|
|
|
* @param array $argv The CLI arguments. |
304
|
|
|
* @return array An array of the resolved name and modified argv. |
305
|
|
|
*/ |
306
|
|
|
protected function longestCommandName($commands, $argv) |
307
|
|
|
{ |
308
|
|
|
for ($i = 3; $i > 1; $i--) { |
309
|
|
|
$parts = array_slice($argv, 0, $i); |
310
|
|
|
$name = implode(' ', $parts); |
311
|
|
|
if ($commands->has($name)) { |
312
|
|
|
return [$name, array_slice($argv, $i)]; |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
$name = array_shift($argv); |
316
|
|
|
|
317
|
|
|
return [$name, $argv]; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Resolve the command name into a name that exists in the collection. |
322
|
|
|
* |
323
|
|
|
* Apply backwards compatible inflections and aliases. |
324
|
|
|
* Will step forward up to 3 tokens in $argv to generate |
325
|
|
|
* a command name in the CommandCollection. More specific |
326
|
|
|
* command names take precedence over less specific ones. |
327
|
|
|
* |
328
|
|
|
* @param \Cake\Console\CommandCollection $commands The command collection to check. |
329
|
|
|
* @param \Cake\Console\ConsoleIo $io ConsoleIo object for errors. |
330
|
|
|
* @param string $name The name |
331
|
|
|
* @return string The resolved class name |
332
|
|
|
*/ |
333
|
|
|
protected function resolveName($commands, $io, $name) |
334
|
|
|
{ |
335
|
|
|
if (!$name) { |
336
|
|
|
$io->err('<error>No command provided. Choose one of the available commands.</error>', 2); |
337
|
|
|
$name = 'help'; |
338
|
|
|
} |
339
|
|
|
if (isset($this->aliases[$name])) { |
340
|
|
|
$name = $this->aliases[$name]; |
341
|
|
|
} |
342
|
|
|
if (!$commands->has($name)) { |
343
|
|
|
$name = Inflector::underscore($name); |
344
|
|
|
} |
345
|
|
|
if (!$commands->has($name)) { |
346
|
|
|
throw new RuntimeException( |
347
|
|
|
"Unknown command `{$this->root} {$name}`." . |
348
|
|
|
" Run `{$this->root} --help` to get the list of valid commands." |
349
|
|
|
); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
return $name; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Execute a Command class. |
357
|
|
|
* |
358
|
|
|
* @param \Cake\Console\Command $command The command to run. |
359
|
|
|
* @param array $argv The CLI arguments to invoke. |
360
|
|
|
* @param \Cake\Console\ConsoleIo $io The console io |
361
|
|
|
* @return int Exit code |
362
|
|
|
*/ |
363
|
|
|
protected function runCommand(Command $command, array $argv, ConsoleIo $io) |
364
|
|
|
{ |
365
|
|
|
try { |
366
|
|
|
return $command->run($argv, $io); |
367
|
|
|
} catch (StopException $e) { |
368
|
|
|
return $e->getCode(); |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Execute a Shell class. |
374
|
|
|
* |
375
|
|
|
* @param \Cake\Console\Shell $shell The shell to run. |
376
|
|
|
* @param array $argv The CLI arguments to invoke. |
377
|
|
|
* @return int Exit code |
378
|
|
|
*/ |
379
|
|
|
protected function runShell(Shell $shell, array $argv) |
380
|
|
|
{ |
381
|
|
|
try { |
382
|
|
|
$shell->initialize(); |
383
|
|
|
|
384
|
|
|
return $shell->runCommand($argv, true); |
|
|
|
|
385
|
|
|
} catch (StopException $e) { |
386
|
|
|
return $e->getCode(); |
387
|
|
|
} |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* The wrapper for creating shell instances. |
392
|
|
|
* |
393
|
|
|
* @param string $className Shell class name. |
394
|
|
|
* @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class. |
395
|
|
|
* @return \Cake\Console\Shell|\Cake\Console\Command |
396
|
|
|
*/ |
397
|
|
|
protected function createShell($className, ConsoleIo $io) |
398
|
|
|
{ |
399
|
|
|
$shell = $this->factory->create($className); |
400
|
|
|
if ($shell instanceof Shell) { |
401
|
|
|
$shell->setIo($io); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
return $shell; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Ensure that the application's routes are loaded. |
409
|
|
|
* |
410
|
|
|
* Console commands and shells often need to generate URLs. |
411
|
|
|
* |
412
|
|
|
* @return void |
413
|
|
|
*/ |
414
|
|
View Code Duplication |
protected function loadRoutes() |
415
|
|
|
{ |
416
|
|
|
$builder = Router::createRouteBuilder('/'); |
417
|
|
|
|
418
|
|
|
if ($this->app instanceof HttpApplicationInterface) { |
419
|
|
|
$this->app->routes($builder); |
420
|
|
|
} |
421
|
|
|
if ($this->app instanceof PluginApplicationInterface) { |
422
|
|
|
$this->app->pluginRoutes($builder); |
423
|
|
|
} |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.