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 1.2.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\Core\App;
20: use Cake\Datasource\ModelAwareTrait;
21: use Cake\Filesystem\File;
22: use Cake\Log\LogTrait;
23: use Cake\ORM\Locator\LocatorAwareTrait;
24: use Cake\ORM\Locator\LocatorInterface;
25: use Cake\Utility\Inflector;
26: use Cake\Utility\MergeVariablesTrait;
27: use Cake\Utility\Text;
28: use ReflectionException;
29: use ReflectionMethod;
30: use RuntimeException;
31:
32: /**
33: * Base class for command-line utilities for automating programmer chores.
34: *
35: * Is the equivalent of Cake\Controller\Controller on the command line.
36: *
37: * @method int|bool|null main(...$args)
38: */
39: class Shell
40: {
41:
42: use LocatorAwareTrait;
43: use LogTrait;
44: use MergeVariablesTrait;
45: use ModelAwareTrait;
46:
47: /**
48: * Default error code
49: *
50: * @var int
51: */
52: const CODE_ERROR = 1;
53:
54: /**
55: * Default success code
56: *
57: * @var int
58: */
59: const CODE_SUCCESS = 0;
60:
61: /**
62: * Output constant making verbose shells.
63: *
64: * @var int
65: */
66: const VERBOSE = ConsoleIo::VERBOSE;
67:
68: /**
69: * Output constant for making normal shells.
70: *
71: * @var int
72: */
73: const NORMAL = ConsoleIo::NORMAL;
74:
75: /**
76: * Output constants for making quiet shells.
77: *
78: * @var int
79: */
80: const QUIET = ConsoleIo::QUIET;
81:
82: /**
83: * An instance of ConsoleOptionParser that has been configured for this class.
84: *
85: * @var \Cake\Console\ConsoleOptionParser
86: */
87: public $OptionParser;
88:
89: /**
90: * If true, the script will ask for permission to perform actions.
91: *
92: * @var bool
93: */
94: public $interactive = true;
95:
96: /**
97: * Contains command switches parsed from the command line.
98: *
99: * @var array
100: */
101: public $params = [];
102:
103: /**
104: * The command (method/task) that is being run.
105: *
106: * @var string
107: */
108: public $command;
109:
110: /**
111: * Contains arguments parsed from the command line.
112: *
113: * @var array
114: */
115: public $args = [];
116:
117: /**
118: * The name of the shell in camelized.
119: *
120: * @var string
121: */
122: public $name;
123:
124: /**
125: * The name of the plugin the shell belongs to.
126: * Is automatically set by ShellDispatcher when a shell is constructed.
127: *
128: * @var string
129: */
130: public $plugin;
131:
132: /**
133: * Contains tasks to load and instantiate
134: *
135: * @var array|bool
136: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::$tasks
137: */
138: public $tasks = [];
139:
140: /**
141: * Contains the loaded tasks
142: *
143: * @var array
144: */
145: public $taskNames = [];
146:
147: /**
148: * Task Collection for the command, used to create Tasks.
149: *
150: * @var \Cake\Console\TaskRegistry
151: */
152: public $Tasks;
153:
154: /**
155: * Normalized map of tasks.
156: *
157: * @var array
158: */
159: protected $_taskMap = [];
160:
161: /**
162: * ConsoleIo instance.
163: *
164: * @var \Cake\Console\ConsoleIo
165: */
166: protected $_io;
167:
168: /**
169: * The root command name used when generating help output.
170: *
171: * @var string
172: */
173: protected $rootName = 'cake';
174:
175: /**
176: * Constructs this Shell instance.
177: *
178: * @param \Cake\Console\ConsoleIo|null $io An io instance.
179: * @param \Cake\ORM\Locator\LocatorInterface|null $locator Table locator instance.
180: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell
181: */
182: public function __construct(ConsoleIo $io = null, LocatorInterface $locator = null)
183: {
184: if (!$this->name) {
185: list(, $class) = namespaceSplit(get_class($this));
186: $this->name = str_replace(['Shell', 'Task'], '', $class);
187: }
188: $this->_io = $io ?: new ConsoleIo();
189: $this->_tableLocator = $locator;
190:
191: $this->modelFactory('Table', [$this->getTableLocator(), 'get']);
192: $this->Tasks = new TaskRegistry($this);
193:
194: $this->_mergeVars(
195: ['tasks'],
196: ['associative' => ['tasks']]
197: );
198:
199: if (isset($this->modelClass)) {
200: $this->loadModel();
201: }
202: }
203:
204: /**
205: * Set the root command name for help output.
206: *
207: * @param string $name The name of the root command.
208: * @return $this
209: */
210: public function setRootName($name)
211: {
212: $this->rootName = (string)$name;
213:
214: return $this;
215: }
216:
217: /**
218: * Get the io object for this shell.
219: *
220: * @return \Cake\Console\ConsoleIo The current ConsoleIo object.
221: */
222: public function getIo()
223: {
224: return $this->_io;
225: }
226:
227: /**
228: * Set the io object for this shell.
229: *
230: * @param \Cake\Console\ConsoleIo $io The ConsoleIo object to use.
231: * @return void
232: */
233: public function setIo(ConsoleIo $io)
234: {
235: $this->_io = $io;
236: }
237:
238: /**
239: * Get/Set the io object for this shell.
240: *
241: * @deprecated 3.5.0 Use getIo()/setIo() instead.
242: * @param \Cake\Console\ConsoleIo|null $io The ConsoleIo object to use.
243: * @return \Cake\Console\ConsoleIo The current ConsoleIo object.
244: */
245: public function io(ConsoleIo $io = null)
246: {
247: deprecationWarning(
248: 'Shell::io() is deprecated. ' .
249: 'Use Shell::setIo()/getIo() instead.'
250: );
251: if ($io !== null) {
252: $this->_io = $io;
253: }
254:
255: return $this->_io;
256: }
257:
258: /**
259: * Initializes the Shell
260: * acts as constructor for subclasses
261: * allows configuration of tasks prior to shell execution
262: *
263: * @return void
264: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Cake\Console\ConsoleOptionParser::initialize
265: */
266: public function initialize()
267: {
268: $this->loadTasks();
269: }
270:
271: /**
272: * Starts up the Shell and displays the welcome message.
273: * Allows for checking and configuring prior to command or main execution
274: *
275: * Override this method if you want to remove the welcome information,
276: * or otherwise modify the pre-command flow.
277: *
278: * @return void
279: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Cake\Console\ConsoleOptionParser::startup
280: */
281: public function startup()
282: {
283: if (!$this->param('requested')) {
284: $this->_welcome();
285: }
286: }
287:
288: /**
289: * Displays a header for the shell
290: *
291: * @return void
292: */
293: protected function _welcome()
294: {
295: }
296:
297: /**
298: * Loads tasks defined in public $tasks
299: *
300: * @return bool
301: */
302: public function loadTasks()
303: {
304: if ($this->tasks === true || empty($this->tasks) || empty($this->Tasks)) {
305: return true;
306: }
307: $this->_taskMap = $this->Tasks->normalizeArray((array)$this->tasks);
308: $this->taskNames = array_merge($this->taskNames, array_keys($this->_taskMap));
309:
310: $this->_validateTasks();
311:
312: return true;
313: }
314:
315: /**
316: * Checks that the tasks in the task map are actually available
317: *
318: * @throws \RuntimeException
319: * @return void
320: */
321: protected function _validateTasks()
322: {
323: foreach ($this->_taskMap as $taskName => $task) {
324: $class = App::className($task['class'], 'Shell/Task', 'Task');
325: if (!class_exists($class)) {
326: throw new RuntimeException(sprintf(
327: 'Task `%s` not found. Maybe you made a typo or a plugin is missing or not loaded?',
328: $taskName
329: ));
330: }
331: }
332: }
333:
334: /**
335: * Check to see if this shell has a task with the provided name.
336: *
337: * @param string $task The task name to check.
338: * @return bool Success
339: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#shell-tasks
340: */
341: public function hasTask($task)
342: {
343: return isset($this->_taskMap[Inflector::camelize($task)]);
344: }
345:
346: /**
347: * Check to see if this shell has a callable method by the given name.
348: *
349: * @param string $name The method name to check.
350: * @return bool
351: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#shell-tasks
352: */
353: public function hasMethod($name)
354: {
355: try {
356: $method = new ReflectionMethod($this, $name);
357: if (!$method->isPublic()) {
358: return false;
359: }
360:
361: return $method->getDeclaringClass()->name !== 'Cake\Console\Shell';
362: } catch (ReflectionException $e) {
363: return false;
364: }
365: }
366:
367: /**
368: * Dispatch a command to another Shell. Similar to Object::requestAction()
369: * but intended for running shells from other shells.
370: *
371: * ### Usage:
372: *
373: * With a string command:
374: *
375: * ```
376: * return $this->dispatchShell('schema create DbAcl');
377: * ```
378: *
379: * Avoid using this form if you have string arguments, with spaces in them.
380: * The dispatched will be invoked incorrectly. Only use this form for simple
381: * command dispatching.
382: *
383: * With an array command:
384: *
385: * ```
386: * return $this->dispatchShell('schema', 'create', 'i18n', '--dry');
387: * ```
388: *
389: * With an array having two key / value pairs:
390: * - `command` can accept either a string or an array. Represents the command to dispatch
391: * - `extra` can accept an array of extra parameters to pass on to the dispatcher. This
392: * parameters will be available in the `param` property of the called `Shell`
393: *
394: * `return $this->dispatchShell([
395: * 'command' => 'schema create DbAcl',
396: * 'extra' => ['param' => 'value']
397: * ]);`
398: *
399: * or
400: *
401: * `return $this->dispatchShell([
402: * 'command' => ['schema', 'create', 'DbAcl'],
403: * 'extra' => ['param' => 'value']
404: * ]);`
405: *
406: * @return int The cli command exit code. 0 is success.
407: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#invoking-other-shells-from-your-shell
408: */
409: public function dispatchShell()
410: {
411: list($args, $extra) = $this->parseDispatchArguments(func_get_args());
412:
413: if (!isset($extra['requested'])) {
414: $extra['requested'] = true;
415: }
416:
417: $dispatcher = new ShellDispatcher($args, false);
418:
419: return $dispatcher->dispatch($extra);
420: }
421:
422: /**
423: * Parses the arguments for the dispatchShell() method.
424: *
425: * @param array $args Arguments fetch from the dispatchShell() method with
426: * func_get_args()
427: * @return array First value has to be an array of the command arguments.
428: * Second value has to be an array of extra parameter to pass on to the dispatcher
429: */
430: public function parseDispatchArguments($args)
431: {
432: $extra = [];
433:
434: if (is_string($args[0]) && count($args) === 1) {
435: $args = explode(' ', $args[0]);
436:
437: return [$args, $extra];
438: }
439:
440: if (is_array($args[0]) && !empty($args[0]['command'])) {
441: $command = $args[0]['command'];
442: if (is_string($command)) {
443: $command = explode(' ', $command);
444: }
445:
446: if (!empty($args[0]['extra'])) {
447: $extra = $args[0]['extra'];
448: }
449:
450: return [$command, $extra];
451: }
452:
453: return [$args, $extra];
454: }
455:
456: /**
457: * Runs the Shell with the provided argv.
458: *
459: * Delegates calls to Tasks and resolves methods inside the class. Commands are looked
460: * up with the following order:
461: *
462: * - Method on the shell.
463: * - Matching task name.
464: * - `main()` method.
465: *
466: * If a shell implements a `main()` method, all missing method calls will be sent to
467: * `main()` with the original method name in the argv.
468: *
469: * For tasks to be invoked they *must* be exposed as subcommands. If you define any subcommands,
470: * you must define all the subcommands your shell needs, whether they be methods on this class
471: * or methods on tasks.
472: *
473: * @param array $argv Array of arguments to run the shell with. This array should be missing the shell name.
474: * @param bool $autoMethod Set to true to allow any public method to be called even if it
475: * was not defined as a subcommand. This is used by ShellDispatcher to make building simple shells easy.
476: * @param array $extra Extra parameters that you can manually pass to the Shell
477: * to be dispatched.
478: * Built-in extra parameter is :
479: * - `requested` : if used, will prevent the Shell welcome message to be displayed
480: * @return int|bool|null
481: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#the-cakephp-console
482: */
483: public function runCommand($argv, $autoMethod = false, $extra = [])
484: {
485: $command = isset($argv[0]) ? Inflector::underscore($argv[0]) : null;
486: $this->OptionParser = $this->getOptionParser();
487: try {
488: list($this->params, $this->args) = $this->OptionParser->parse($argv);
489: } catch (ConsoleException $e) {
490: $this->err('Error: ' . $e->getMessage());
491:
492: return false;
493: }
494:
495: if (!empty($extra) && is_array($extra)) {
496: $this->params = array_merge($this->params, $extra);
497: }
498: $this->_setOutputLevel();
499: $this->command = $command;
500: if (!empty($this->params['help'])) {
501: return $this->_displayHelp($command);
502: }
503:
504: $subcommands = $this->OptionParser->subcommands();
505: $method = Inflector::camelize($command);
506: $isMethod = $this->hasMethod($method);
507:
508: if ($isMethod && $autoMethod && count($subcommands) === 0) {
509: array_shift($this->args);
510: $this->startup();
511:
512: return $this->$method(...$this->args);
513: }
514:
515: if ($isMethod && isset($subcommands[$command])) {
516: $this->startup();
517:
518: return $this->$method(...$this->args);
519: }
520:
521: if ($this->hasTask($command) && isset($subcommands[$command])) {
522: $this->startup();
523: array_shift($argv);
524:
525: return $this->{$method}->runCommand($argv, false, ['requested' => true]);
526: }
527:
528: if ($this->hasMethod('main')) {
529: $this->command = 'main';
530: $this->startup();
531:
532: return $this->main(...$this->args);
533: }
534:
535: $this->err('No subcommand provided. Choose one of the available subcommands.', 2);
536: $this->_io->err($this->OptionParser->help($command));
537:
538: return false;
539: }
540:
541: /**
542: * Set the output level based on the parameters.
543: *
544: * This reconfigures both the output level for out()
545: * and the configured stdout/stderr logging
546: *
547: * @return void
548: */
549: protected function _setOutputLevel()
550: {
551: $this->_io->setLoggers(ConsoleIo::NORMAL);
552: if (!empty($this->params['quiet'])) {
553: $this->_io->level(ConsoleIo::QUIET);
554: $this->_io->setLoggers(ConsoleIo::QUIET);
555: }
556: if (!empty($this->params['verbose'])) {
557: $this->_io->level(ConsoleIo::VERBOSE);
558: $this->_io->setLoggers(ConsoleIo::VERBOSE);
559: }
560: }
561:
562: /**
563: * Display the help in the correct format
564: *
565: * @param string $command The command to get help for.
566: * @return int|bool The number of bytes returned from writing to stdout.
567: */
568: protected function _displayHelp($command)
569: {
570: $format = 'text';
571: if (!empty($this->args[0]) && $this->args[0] === 'xml') {
572: $format = 'xml';
573: $this->_io->setOutputAs(ConsoleOutput::RAW);
574: } else {
575: $this->_welcome();
576: }
577:
578: $subcommands = $this->OptionParser->subcommands();
579: $command = isset($subcommands[$command]) ? $command : null;
580:
581: return $this->out($this->OptionParser->help($command, $format));
582: }
583:
584: /**
585: * Gets the option parser instance and configures it.
586: *
587: * By overriding this method you can configure the ConsoleOptionParser before returning it.
588: *
589: * @return \Cake\Console\ConsoleOptionParser
590: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#configuring-options-and-generating-help
591: */
592: public function getOptionParser()
593: {
594: $name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
595: $parser = new ConsoleOptionParser($name);
596: $parser->setRootName($this->rootName);
597:
598: return $parser;
599: }
600:
601: /**
602: * Overload get for lazy building of tasks
603: *
604: * @param string $name The task to get.
605: * @return \Cake\Console\Shell Object of Task
606: */
607: public function __get($name)
608: {
609: if (empty($this->{$name}) && in_array($name, $this->taskNames)) {
610: $properties = $this->_taskMap[$name];
611: $this->{$name} = $this->Tasks->load($properties['class'], $properties['config']);
612: $this->{$name}->args =& $this->args;
613: $this->{$name}->params =& $this->params;
614: $this->{$name}->initialize();
615: $this->{$name}->loadTasks();
616: }
617:
618: return $this->{$name};
619: }
620:
621: /**
622: * Safely access the values in $this->params.
623: *
624: * @param string $name The name of the parameter to get.
625: * @return string|bool|null Value. Will return null if it doesn't exist.
626: */
627: public function param($name)
628: {
629: if (!isset($this->params[$name])) {
630: return null;
631: }
632:
633: return $this->params[$name];
634: }
635:
636: /**
637: * Prompts the user for input, and returns it.
638: *
639: * @param string $prompt Prompt text.
640: * @param string|array|null $options Array or string of options.
641: * @param string|null $default Default input value.
642: * @return mixed Either the default value, or the user-provided input.
643: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::in
644: */
645: public function in($prompt, $options = null, $default = null)
646: {
647: if (!$this->interactive) {
648: return $default;
649: }
650: if ($options) {
651: return $this->_io->askChoice($prompt, $options, $default);
652: }
653:
654: return $this->_io->ask($prompt, $default);
655: }
656:
657: /**
658: * Wrap a block of text.
659: * Allows you to set the width, and indenting on a block of text.
660: *
661: * ### Options
662: *
663: * - `width` The width to wrap to. Defaults to 72
664: * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true.
665: * - `indent` Indent the text with the string provided. Defaults to null.
666: *
667: * @param string $text Text the text to format.
668: * @param int|array $options Array of options to use, or an integer to wrap the text to.
669: * @return string Wrapped / indented text
670: * @see \Cake\Utility\Text::wrap()
671: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::wrapText
672: */
673: public function wrapText($text, $options = [])
674: {
675: return Text::wrap($text, $options);
676: }
677:
678: /**
679: * Output at the verbose level.
680: *
681: * @param string|array $message A string or an array of strings to output
682: * @param int $newlines Number of newlines to append
683: * @return int|bool The number of bytes returned from writing to stdout.
684: */
685: public function verbose($message, $newlines = 1)
686: {
687: return $this->_io->verbose($message, $newlines);
688: }
689:
690: /**
691: * Output at all levels.
692: *
693: * @param string|array $message A string or an array of strings to output
694: * @param int $newlines Number of newlines to append
695: * @return int|bool The number of bytes returned from writing to stdout.
696: */
697: public function quiet($message, $newlines = 1)
698: {
699: return $this->_io->quiet($message, $newlines);
700: }
701:
702: /**
703: * Outputs a single or multiple messages to stdout. If no parameters
704: * are passed outputs just a newline.
705: *
706: * ### Output levels
707: *
708: * There are 3 built-in output level. Shell::QUIET, Shell::NORMAL, Shell::VERBOSE.
709: * The verbose and quiet output levels, map to the `verbose` and `quiet` output switches
710: * present in most shells. Using Shell::QUIET for a message means it will always display.
711: * While using Shell::VERBOSE means it will only display when verbose output is toggled.
712: *
713: * @param string|array|null $message A string or an array of strings to output
714: * @param int $newlines Number of newlines to append
715: * @param int $level The message's output level, see above.
716: * @return int|bool The number of bytes returned from writing to stdout.
717: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::out
718: */
719: public function out($message = null, $newlines = 1, $level = Shell::NORMAL)
720: {
721: return $this->_io->out($message, $newlines, $level);
722: }
723:
724: /**
725: * Outputs a single or multiple error messages to stderr. If no parameters
726: * are passed outputs just a newline.
727: *
728: * @param string|array|null $message A string or an array of strings to output
729: * @param int $newlines Number of newlines to append
730: * @return int|bool The number of bytes returned from writing to stderr.
731: */
732: public function err($message = null, $newlines = 1)
733: {
734: return $this->_io->error($message, $newlines);
735: }
736:
737: /**
738: * Convenience method for out() that wraps message between <info /> tag
739: *
740: * @param string|array|null $message A string or an array of strings to output
741: * @param int $newlines Number of newlines to append
742: * @param int $level The message's output level, see above.
743: * @return int|bool The number of bytes returned from writing to stdout.
744: * @see https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::out
745: */
746: public function info($message = null, $newlines = 1, $level = Shell::NORMAL)
747: {
748: return $this->_io->info($message, $newlines, $level);
749: }
750:
751: /**
752: * Convenience method for err() that wraps message between <warning /> tag
753: *
754: * @param string|array|null $message A string or an array of strings to output
755: * @param int $newlines Number of newlines to append
756: * @return int|bool The number of bytes returned from writing to stderr.
757: * @see https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::err
758: */
759: public function warn($message = null, $newlines = 1)
760: {
761: return $this->_io->warning($message, $newlines);
762: }
763:
764: /**
765: * Convenience method for out() that wraps message between <success /> tag
766: *
767: * @param string|array|null $message A string or an array of strings to output
768: * @param int $newlines Number of newlines to append
769: * @param int $level The message's output level, see above.
770: * @return int|bool The number of bytes returned from writing to stdout.
771: * @see https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::out
772: */
773: public function success($message = null, $newlines = 1, $level = Shell::NORMAL)
774: {
775: return $this->_io->success($message, $newlines, $level);
776: }
777:
778: /**
779: * Wraps a message with a given message type, e.g. <warning>
780: *
781: * @param string $messageType The message type, e.g. "warning".
782: * @param string|array $message The message to wrap.
783: * @return array|string The message wrapped with the given message type.
784: * @deprecated 3.6.0 Will be removed in 4.0.0 as it is no longer in use.
785: */
786: protected function wrapMessageWithType($messageType, $message)
787: {
788: deprecationWarning(
789: 'Shell::wrapMessageWithType() is deprecated. ' .
790: 'Use output methods on ConsoleIo instead.'
791: );
792: if (is_array($message)) {
793: foreach ($message as $k => $v) {
794: $message[$k] = "<$messageType>" . $v . "</$messageType>";
795: }
796: } else {
797: $message = "<$messageType>" . $message . "</$messageType>";
798: }
799:
800: return $message;
801: }
802:
803: /**
804: * Returns a single or multiple linefeeds sequences.
805: *
806: * @param int $multiplier Number of times the linefeed sequence should be repeated
807: * @return string
808: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::nl
809: */
810: public function nl($multiplier = 1)
811: {
812: return $this->_io->nl($multiplier);
813: }
814:
815: /**
816: * Outputs a series of minus characters to the standard output, acts as a visual separator.
817: *
818: * @param int $newlines Number of newlines to pre- and append
819: * @param int $width Width of the line, defaults to 63
820: * @return void
821: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::hr
822: */
823: public function hr($newlines = 0, $width = 63)
824: {
825: $this->_io->hr($newlines, $width);
826: }
827:
828: /**
829: * Displays a formatted error message
830: * and exits the application with status code 1
831: *
832: * @param string $message The error message
833: * @param int $exitCode The exit code for the shell task.
834: * @throws \Cake\Console\Exception\StopException
835: * @return void
836: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#styling-output
837: */
838: public function abort($message, $exitCode = self::CODE_ERROR)
839: {
840: $this->_io->err('<error>' . $message . '</error>');
841: throw new StopException($message, $exitCode);
842: }
843:
844: /**
845: * Displays a formatted error message
846: * and exits the application with status code 1
847: *
848: * @param string $title Title of the error
849: * @param string|null $message An optional error message
850: * @param int $exitCode The exit code for the shell task.
851: * @throws \Cake\Console\Exception\StopException
852: * @return int Error code
853: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#styling-output
854: * @deprecated 3.2.0 Use Shell::abort() instead.
855: */
856: public function error($title, $message = null, $exitCode = self::CODE_ERROR)
857: {
858: deprecationWarning('Shell::error() is deprecated. `Use Shell::abort() instead.');
859: $this->_io->err(sprintf('<error>Error:</error> %s', $title));
860:
861: if (!empty($message)) {
862: $this->_io->err($message);
863: }
864:
865: $this->_stop($exitCode);
866:
867: return $exitCode;
868: }
869:
870: /**
871: * Clear the console
872: *
873: * @return void
874: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#console-output
875: */
876: public function clear()
877: {
878: if (empty($this->params['noclear'])) {
879: if (DIRECTORY_SEPARATOR === '/') {
880: passthru('clear');
881: } else {
882: passthru('cls');
883: }
884: }
885: }
886:
887: /**
888: * Creates a file at given path
889: *
890: * @param string $path Where to put the file.
891: * @param string $contents Content to put in the file.
892: * @return bool Success
893: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#creating-files
894: */
895: public function createFile($path, $contents)
896: {
897: $path = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $path);
898:
899: $this->_io->out();
900:
901: $fileExists = is_file($path);
902: if ($fileExists && empty($this->params['force']) && !$this->interactive) {
903: $this->_io->out('<warning>File exists, skipping</warning>.');
904:
905: return false;
906: }
907:
908: if ($fileExists && $this->interactive && empty($this->params['force'])) {
909: $this->_io->out(sprintf('<warning>File `%s` exists</warning>', $path));
910: $key = $this->_io->askChoice('Do you want to overwrite?', ['y', 'n', 'a', 'q'], 'n');
911:
912: if (strtolower($key) === 'q') {
913: $this->_io->out('<error>Quitting</error>.', 2);
914: $this->_stop();
915:
916: return false;
917: }
918: if (strtolower($key) === 'a') {
919: $this->params['force'] = true;
920: $key = 'y';
921: }
922: if (strtolower($key) !== 'y') {
923: $this->_io->out(sprintf('Skip `%s`', $path), 2);
924:
925: return false;
926: }
927: } else {
928: $this->out(sprintf('Creating file %s', $path));
929: }
930:
931: $File = new File($path, true);
932:
933: try {
934: if ($File->exists() && $File->writable()) {
935: $File->write($contents);
936: $this->_io->out(sprintf('<success>Wrote</success> `%s`', $path));
937:
938: return true;
939: }
940:
941: $this->_io->err(sprintf('<error>Could not write to `%s`</error>.', $path), 2);
942:
943: return false;
944: } finally {
945: $File->close();
946: }
947: }
948:
949: /**
950: * Makes absolute file path easier to read
951: *
952: * @param string $file Absolute file path
953: * @return string short path
954: * @link https://book.cakephp.org/3.0/en/console-and-shells.html#Shell::shortPath
955: */
956: public function shortPath($file)
957: {
958: $shortPath = str_replace(ROOT, null, $file);
959: $shortPath = str_replace('..' . DIRECTORY_SEPARATOR, '', $shortPath);
960: $shortPath = str_replace(DIRECTORY_SEPARATOR, '/', $shortPath);
961:
962: return str_replace('//', DIRECTORY_SEPARATOR, $shortPath);
963: }
964:
965: /**
966: * Render a Console Helper
967: *
968: * Create and render the output for a helper object. If the helper
969: * object has not already been loaded, it will be loaded and constructed.
970: *
971: * @param string $name The name of the helper to render
972: * @param array $settings Configuration data for the helper.
973: * @return \Cake\Console\Helper The created helper instance.
974: */
975: public function helper($name, array $settings = [])
976: {
977: return $this->_io->helper($name, $settings);
978: }
979:
980: /**
981: * Stop execution of the current script.
982: * Raises a StopException to try and halt the execution.
983: *
984: * @param int|string $status see https://secure.php.net/exit for values
985: * @throws \Cake\Console\Exception\StopException
986: * @return void
987: */
988: protected function _stop($status = self::CODE_SUCCESS)
989: {
990: throw new StopException('Halting error reached', $status);
991: }
992:
993: /**
994: * Returns an array that can be used to describe the internal state of this
995: * object.
996: *
997: * @return array
998: */
999: public function __debugInfo()
1000: {
1001: return [
1002: 'name' => $this->name,
1003: 'plugin' => $this->plugin,
1004: 'command' => $this->command,
1005: 'tasks' => $this->tasks,
1006: 'params' => $this->params,
1007: 'args' => $this->args,
1008: 'interactive' => $this->interactive,
1009: ];
1010: }
1011: }
1012: