Shell::loadTasks()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 0
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
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/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/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/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/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/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/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/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/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 View Code Duplication
        if (!empty($this->params['quiet'])) {
552
            $this->_io->level(ConsoleIo::QUIET);
553
            $this->_io->setLoggers(ConsoleIo::QUIET);
554
        }
555 View Code Duplication
        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/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/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/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/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);
0 ignored issues
show
Bug introduced by
It seems like $message defined by parameter $message on line 718 can also be of type null; however, Cake\Console\ConsoleIo::out() does only seem to accept string|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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/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/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/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/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/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/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/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/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/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/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