Completed
Push — fix-numbervalidator-comma-deci... ( 08054b...a7f0a3 )
by Alexander
40:41 queued 37:41
created

HelpController::getCommands()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\console\controllers;
9
10
use Yii;
11
use yii\base\Application;
12
use yii\console\Controller;
13
use yii\console\Exception;
14
use yii\helpers\Console;
15
use yii\helpers\Inflector;
16
17
/**
18
 * Provides help information about console commands.
19
 *
20
 * This command displays the available command list in
21
 * the application or the detailed instructions about using
22
 * a specific command.
23
 *
24
 * This command can be used as follows on command line:
25
 *
26
 * ```
27
 * yii help [command name]
28
 * ```
29
 *
30
 * In the above, if the command name is not provided, all
31
 * available commands will be displayed.
32
 *
33
 * @property array $commands All available command names. This property is read-only.
34
 *
35
 * @author Qiang Xue <[email protected]>
36
 * @since 2.0
37
 */
38
class HelpController extends Controller
39
{
40
    /**
41
     * Displays available commands or the detailed information
42
     * about a particular command.
43
     *
44
     * @param string $command The name of the command to show help about.
45
     * If not provided, all available commands will be displayed.
46
     * @return int the exit status
47
     * @throws Exception if the command for help is unknown
48
     */
49 3
    public function actionIndex($command = null)
50
    {
51 3
        if ($command !== null) {
52 2
            $result = Yii::$app->createController($command);
53 2
            if ($result === false) {
54
                $name = $this->ansiFormat($command, Console::FG_YELLOW);
55
                throw new Exception("No help for unknown command \"$name\".");
56
            }
57
58 2
            list($controller, $actionID) = $result;
59
60 2
            $actions = $this->getActions($controller);
61 2
            if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
62 2
                $this->getSubCommandHelp($controller, $actionID);
63 2
            } else {
64
                $this->getCommandHelp($controller);
65
            }
66 2
        } else {
67 1
            $this->getDefaultHelp();
68
        }
69 3
    }
70
71
    /**
72
     * List all available controllers and actions in machine readable format.
73
     * This is used for shell completion.
74
     * @since 2.0.11
75
     */
76 1
    public function actionList()
77
    {
78 1
        $commands = $this->getCommandDescriptions();
79 1
        foreach ($commands as $command => $description) {
80 1
            $result = Yii::$app->createController($command);
81 1
            if ($result === false || !($result[0] instanceof Controller)) {
82
                continue;
83
            }
84
            /** @var $controller Controller */
85 1
            list($controller, $actionID) = $result;
0 ignored issues
show
Unused Code introduced by
The assignment to $actionID is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
86 1
            $actions = $this->getActions($controller);
87 1
            if (!empty($actions)) {
88 1
                $prefix = $controller->getUniqueId();
89 1
                $this->stdout("$prefix\n");
90 1
                foreach ($actions as $action) {
91 1
                    $this->stdout("$prefix/$action\n");
92 1
                }
93 1
            }
94 1
        }
95 1
    }
96
97
    /**
98
     * List all available options for the $action in machine readable format.
99
     * This is used for shell completion.
100
     *
101
     * @param string $action route to action
102
     * @since 2.0.11
103
     */
104 1
    public function actionListActionOptions($action)
105
    {
106 1
        $result = Yii::$app->createController($action);
107
108 1
        if ($result === false || !($result[0] instanceof Controller)) {
109
            return;
110
        }
111
112
        /** @var Controller $controller */
113 1
        list($controller, $actionID) = $result;
114 1
        $action = $controller->createAction($actionID);
115 1
        if ($action === null) {
116
            return;
117
        }
118
119 1
        $arguments = $controller->getActionArgsHelp($action);
120 1
        foreach ($arguments as $argument => $help) {
121 1
            $description = str_replace("\n", '', addcslashes($help['comment'], ':')) ?: $argument;
122 1
            $this->stdout($argument . ':' . $description . "\n");
123 1
        }
124
125 1
        $this->stdout("\n");
126 1
        $options = $controller->getActionOptionsHelp($action);
127 1
        foreach ($options as $argument => $help) {
128 1
            $description = str_replace("\n", '', addcslashes($help['comment'], ':')) ?: $argument;
129 1
            $this->stdout('--' . $argument . ':' . $description . "\n");
130 1
        }
131 1
    }
132
133
    /**
134
     * Displays usage information for $action
135
     *
136
     * @param string $action route to action
137
     * @since 2.0.11
138
     */
139 1
    public function actionUsage($action)
140
    {
141 1
        $result = Yii::$app->createController($action);
142
143 1
        if ($result === false || !($result[0] instanceof Controller)) {
144
            return;
145
        }
146
147
        /** @var Controller $controller */
148 1
        list($controller, $actionID) = $result;
149 1
        $action = $controller->createAction($actionID);
150 1
        if ($action === null) {
151
            return;
152
        }
153
154 1
        $scriptName = $this->getScriptName();
155 1
        if ($action->id === $controller->defaultAction) {
156
            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
157
        } else {
158 1
            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
159
        }
160
161 1
        $args = $controller->getActionArgsHelp($action);
162 1
        foreach ($args as $name => $arg) {
163 1
            if ($arg['required']) {
164 1
                $this->stdout(' <' . $name . '>', Console::FG_CYAN);
165 1
            } else {
166
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
167
            }
168 1
        }
169
170 1
        $this->stdout("\n");
171
172 1
        return;
173
    }
174
175
    /**
176
     * Returns all available command names.
177
     * @return array all available command names
178
     */
179 17
    public function getCommands()
180
    {
181 17
        $commands = $this->getModuleCommands(Yii::$app);
182 17
        sort($commands);
183 17
        return array_unique($commands);
184
    }
185
186
    /**
187
     * Returns an array of commands an their descriptions.
188
     * @return array all available commands as keys and their description as values.
189
     */
190 2
    protected function getCommandDescriptions()
191
    {
192 2
        $descriptions = [];
193 2
        foreach ($this->getCommands() as $command) {
194 2
            $description = '';
195
196 2
            $result = Yii::$app->createController($command);
197 2
            if ($result !== false && $result[0] instanceof Controller) {
198 2
                list($controller, $actionID) = $result;
0 ignored issues
show
Unused Code introduced by
The assignment to $actionID is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
199
                /** @var Controller $controller */
200 2
                $description = $controller->getHelpSummary();
201 2
            }
202
203 2
            $descriptions[$command] = $description;
204 2
        }
205
206 2
        return $descriptions;
207
    }
208
209
    /**
210
     * Returns all available actions of the specified controller.
211
     * @param Controller $controller the controller instance
212
     * @return array all available action IDs.
213
     */
214 19
    public function getActions($controller)
215
    {
216 19
        $actions = array_keys($controller->actions());
217 19
        $class = new \ReflectionClass($controller);
218 19
        foreach ($class->getMethods() as $method) {
219 19
            $name = $method->getName();
220 19
            if ($name !== 'actions' && $method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) {
221 19
                $actions[] = Inflector::camel2id(substr($name, 6), '-', true);
222 19
            }
223 19
        }
224 19
        sort($actions);
225
226 19
        return array_unique($actions);
227
    }
228
229
    /**
230
     * Returns available commands of a specified module.
231
     * @param \yii\base\Module $module the module instance
232
     * @return array the available command names
233
     */
234 17
    protected function getModuleCommands($module)
235
    {
236 17
        $prefix = $module instanceof Application ? '' : $module->getUniqueId() . '/';
237
238 17
        $commands = [];
239 17
        foreach (array_keys($module->controllerMap) as $id) {
240 17
            $commands[] = $prefix . $id;
241 17
        }
242
243 17
        foreach ($module->getModules() as $id => $child) {
244
            if (($child = $module->getModule($id)) === null) {
245
                continue;
246
            }
247
            foreach ($this->getModuleCommands($child) as $command) {
248
                $commands[] = $command;
249
            }
250 17
        }
251
252 17
        $controllerPath = $module->getControllerPath();
253 17
        if (is_dir($controllerPath)) {
254
            $files = scandir($controllerPath);
255
            foreach ($files as $file) {
256
                if (!empty($file) && substr_compare($file, 'Controller.php', -14, 14) === 0) {
257
                    $controllerClass = $module->controllerNamespace . '\\' . substr(basename($file), 0, -4);
258
                    if ($this->validateControllerClass($controllerClass)) {
259
                        $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
260
                    }
261
                }
262
            }
263
        }
264
265 17
        return $commands;
266
    }
267
268
    /**
269
     * Validates if the given class is a valid console controller class.
270
     * @param string $controllerClass
271
     * @return bool
272
     */
273
    protected function validateControllerClass($controllerClass)
274
    {
275
        if (class_exists($controllerClass)) {
276
            $class = new \ReflectionClass($controllerClass);
277
            return !$class->isAbstract() && $class->isSubclassOf('yii\console\Controller');
278
        } else {
279
            return false;
280
        }
281
    }
282
283
    /**
284
     * Displays all available commands.
285
     */
286 1
    protected function getDefaultHelp()
287
    {
288 1
        $commands = $this->getCommandDescriptions();
289 1
        $this->stdout($this->getDefaultHelpHeader());
290 1
        if (!empty($commands)) {
291 1
            $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
292 1
            $len = 0;
293 1
            foreach ($commands as $command => $description) {
294 1
                $result = Yii::$app->createController($command);
295 1
                if ($result !== false && $result[0] instanceof Controller) {
296
                    /** @var $controller Controller */
297 1
                    list($controller, $actionID) = $result;
0 ignored issues
show
Unused Code introduced by
The assignment to $actionID is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
298 1
                    $actions = $this->getActions($controller);
299 1
                    if (!empty($actions)) {
300 1
                        $prefix = $controller->getUniqueId();
301 1
                        foreach ($actions as $action) {
302 1
                            $string = $prefix . '/' . $action;
303 1
                            if ($action === $controller->defaultAction) {
304 1
                                $string .= ' (default)';
305 1
                            }
306 1
                            if (($l = strlen($string)) > $len) {
307 1
                                $len = $l;
308 1
                            }
309 1
                        }
310 1
                    }
311 1
                } elseif (($l = strlen($command)) > $len) {
312
                    $len = $l;
313
                }
314 1
            }
315 1
            foreach ($commands as $command => $description) {
316 1
                $this->stdout('- ' . $this->ansiFormat($command, Console::FG_YELLOW));
317 1
                $this->stdout(str_repeat(' ', $len + 4 - strlen($command)));
318 1
                $this->stdout(Console::wrapText($description, $len + 4 + 2), Console::BOLD);
319 1
                $this->stdout("\n");
320
321 1
                $result = Yii::$app->createController($command);
322 1
                if ($result !== false && $result[0] instanceof Controller) {
323 1
                    list($controller, $actionID) = $result;
0 ignored issues
show
Unused Code introduced by
The assignment to $actionID is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
324 1
                    $actions = $this->getActions($controller);
325 1
                    if (!empty($actions)) {
326 1
                        $prefix = $controller->getUniqueId();
327 1
                        foreach ($actions as $action) {
328 1
                            $string = '  ' . $prefix . '/' . $action;
329 1
                            $this->stdout('  ' . $this->ansiFormat($string, Console::FG_GREEN));
330 1
                            if ($action === $controller->defaultAction) {
331 1
                                $string .= ' (default)';
332 1
                                $this->stdout(' (default)', Console::FG_YELLOW);
333 1
                            }
334 1
                            $summary = $controller->getActionHelpSummary($controller->createAction($action));
335 1
                            if ($summary !== '') {
336 1
                                $this->stdout(str_repeat(' ', $len + 4 - strlen($string)));
337 1
                                $this->stdout(Console::wrapText($summary, $len + 4 + 2));
338 1
                            }
339 1
                            $this->stdout("\n");
340 1
                        }
341 1
                    }
342 1
                    $this->stdout("\n");
343 1
                }
344 1
            }
345 1
            $scriptName = $this->getScriptName();
346 1
            $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
347 1
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
348 1
                            . $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n");
349 1
        } else {
350
            $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
351
        }
352 1
    }
353
354
    /**
355
     * Displays the overall information of the command.
356
     * @param Controller $controller the controller instance
357
     */
358
    protected function getCommandHelp($controller)
359
    {
360
        $controller->color = $this->color;
361
362
        $this->stdout("\nDESCRIPTION\n", Console::BOLD);
363
        $comment = $controller->getHelp();
364
        if ($comment !== '') {
365
            $this->stdout("\n$comment\n\n");
366
        }
367
368
        $actions = $this->getActions($controller);
369
        if (!empty($actions)) {
370
            $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
371
            $prefix = $controller->getUniqueId();
372
373
            $maxlen = 5;
374
            foreach ($actions as $action) {
375
                $len = strlen($prefix.'/'.$action) + 2 + ($action === $controller->defaultAction ? 10 : 0);
376
                if ($maxlen < $len) {
377
                    $maxlen = $len;
378
                }
379
            }
380
            foreach ($actions as $action) {
381
                $this->stdout('- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW));
382
                $len = strlen($prefix.'/'.$action) + 2;
383
                if ($action === $controller->defaultAction) {
384
                    $this->stdout(' (default)', Console::FG_GREEN);
385
                    $len += 10;
386
                }
387
                $summary = $controller->getActionHelpSummary($controller->createAction($action));
0 ignored issues
show
Bug introduced by
It seems like $controller->createAction($action) can be null; however, getActionHelpSummary() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
388
                if ($summary !== '') {
389
                    $this->stdout(str_repeat(' ', $maxlen - $len + 2) . Console::wrapText($summary, $maxlen + 2));
390
                }
391
                $this->stdout("\n");
392
            }
393
            $scriptName = $this->getScriptName();
394
            $this->stdout("\nTo see the detailed information about individual sub-commands, enter:\n");
395
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
396
                            . $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n");
397
        }
398
    }
399
400
    /**
401
     * Displays the detailed information of a command action.
402
     * @param Controller $controller the controller instance
403
     * @param string $actionID action ID
404
     * @throws Exception if the action does not exist
405
     */
406 2
    protected function getSubCommandHelp($controller, $actionID)
407
    {
408 2
        $action = $controller->createAction($actionID);
409 2
        if ($action === null) {
410
            $name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW);
411
            throw new Exception("No help for unknown sub-command \"$name\".");
412
        }
413
414 2
        $description = $controller->getActionHelp($action);
415 2
        if ($description !== '') {
416 2
            $this->stdout("\nDESCRIPTION\n", Console::BOLD);
417 2
            $this->stdout("\n$description\n\n");
418 2
        }
419
420 2
        $this->stdout("\nUSAGE\n\n", Console::BOLD);
421 2
        $scriptName = $this->getScriptName();
422 2
        if ($action->id === $controller->defaultAction) {
423 2
            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
424 2
        } else {
425
            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
426
        }
427
428 2
        $args = $controller->getActionArgsHelp($action);
429 2
        foreach ($args as $name => $arg) {
430 2
            if ($arg['required']) {
431
                $this->stdout(' <' . $name . '>', Console::FG_CYAN);
432
            } else {
433 2
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
434
            }
435 2
        }
436
437 2
        $options = $controller->getActionOptionsHelp($action);
438 2
        $options[\yii\console\Application::OPTION_APPCONFIG] = [
439 2
            'type' => 'string',
440 2
            'default' => null,
441 2
            'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
442
        ];
443 2
        ksort($options);
444
445 2
        if (!empty($options)) {
446 2
            $this->stdout(' [...options...]', Console::FG_RED);
447 2
        }
448 2
        $this->stdout("\n\n");
449
450 2
        if (!empty($args)) {
451 2
            foreach ($args as $name => $arg) {
452 2
                $this->stdout($this->formatOptionHelp(
453 2
                        '- ' . $this->ansiFormat($name, Console::FG_CYAN),
454 2
                        $arg['required'],
455 2
                        $arg['type'],
456 2
                        $arg['default'],
457 2
                        $arg['comment']) . "\n\n");
458 2
            }
459 2
        }
460
461 2
        if (!empty($options)) {
462 2
            $this->stdout("\nOPTIONS\n\n", Console::BOLD);
463 2
            foreach ($options as $name => $option) {
464 2
                $this->stdout($this->formatOptionHelp(
465 2
                        $this->ansiFormat('--' . $name . $this->formatOptionAliases($controller, $name), Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD),
466 2
                        !empty($option['required']),
467 2
                        $option['type'],
468 2
                        $option['default'],
469 2
                        $option['comment']) . "\n\n");
470 2
            }
471 2
        }
472 2
    }
473
474
    /**
475
     * Generates a well-formed string for an argument or option.
476
     * @param string $name the name of the argument or option
477
     * @param bool $required whether the argument is required
478
     * @param string $type the type of the option or argument
479
     * @param mixed $defaultValue the default value of the option or argument
480
     * @param string $comment comment about the option or argument
481
     * @return string the formatted string for the argument or option
482
     */
483 2
    protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
484
    {
485 2
        $comment = trim($comment);
486 2
        $type = trim($type);
487 2
        if (strncmp($type, 'bool', 4) === 0) {
488 2
            $type = 'boolean, 0 or 1';
489 2
        }
490
491 2
        if ($defaultValue !== null && !is_array($defaultValue)) {
492 2
            if ($type === null) {
493
                $type = gettype($defaultValue);
494
            }
495 2
            if (is_bool($defaultValue)) {
496
                // show as integer to avoid confusion
497 2
                $defaultValue = (int) $defaultValue;
498 2
            }
499 2
            if (is_string($defaultValue)) {
500 1
                $defaultValue = "'" . $defaultValue . "'";
501 1
            } else {
502 2
                $defaultValue = var_export($defaultValue, true);
503
            }
504 2
            $doc = "$type (defaults to $defaultValue)";
505 2
        } else {
506 2
            $doc = $type;
507
        }
508
509 2
        if ($doc === '') {
510
            $doc = $comment;
511 2
        } elseif ($comment !== '') {
512 2
            $doc .= "\n" . preg_replace('/^/m', '  ', $comment);
513 2
        }
514
515 2
        $name = $required ? "$name (required)" : $name;
516
517 2
        return $doc === '' ? $name : "$name: $doc";
518
    }
519
520
    /**
521
     * @param Controller $controller the controller instance
522
     * @param string $option the option name
523
     * @return string the formatted string for the alias argument or option
524
     * @since 2.0.8
525
     */
526 2
    protected function formatOptionAliases($controller, $option)
527
    {
528 2
        $aliases = $controller->optionAliases();
529 2
        foreach ($aliases as $name => $value) {
530 2
            if ($value === $option) {
531 2
                return ', -' . $name;
532
            }
533 2
        }
534 2
        return '';
535
    }
536
537
    /**
538
     * @return string the name of the cli script currently running.
539
     */
540 4
    protected function getScriptName()
541
    {
542 4
        return basename(Yii::$app->request->scriptFile);
543
    }
544
545
    /**
546
     * Return a default help header.
547
     * @return string default help header.
548
     * @since 2.0.11
549
     */
550 1
    protected function getDefaultHelpHeader()
551
    {
552 1
        return "\nThis is Yii version " . \Yii::getVersion() . ".\n";
553
    }
554
}
555