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