Completed
Push — 2.1 ( 224aac...d335fd )
by Dmitry
54:21 queued 13:00
created

HelpController::getDefaultHelpHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
    public function actionIndex($command = null)
50
    {
51
        if ($command !== null) {
52
            $result = Yii::$app->createController($command);
53
            if ($result === false) {
54
                $name = $this->ansiFormat($command, Console::FG_YELLOW);
55
                throw new Exception("No help for unknown command \"$name\".");
56
            }
57
58
            list($controller, $actionID) = $result;
59
60
            $actions = $this->getActions($controller);
61
            if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
62
                $this->getSubCommandHelp($controller, $actionID);
63
            } else {
64
                $this->getCommandHelp($controller);
65
            }
66
        } else {
67
            $this->getDefaultHelp();
68
        }
69
    }
70
71
    /**
72
     * Returns all available command names.
73
     * @return array all available command names
74
     */
75
    public function getCommands()
76
    {
77
        $commands = $this->getModuleCommands(Yii::$app);
78
        sort($commands);
79
        return array_unique($commands);
80
    }
81
82
    /**
83
     * Returns an array of commands an their descriptions.
84
     * @return array all available commands as keys and their description as values.
85
     */
86
    protected function getCommandDescriptions()
87
    {
88
        $descriptions = [];
89
        foreach ($this->getCommands() as $command) {
90
            $description = '';
91
92
            $result = Yii::$app->createController($command);
93
            if ($result !== false && $result[0] instanceof Controller) {
94
                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...
95
                /** @var Controller $controller */
96
                $description = $controller->getHelpSummary();
97
            }
98
99
            $descriptions[$command] = $description;
100
        }
101
102
        return $descriptions;
103
    }
104
105
    /**
106
     * Returns all available actions of the specified controller.
107
     * @param Controller $controller the controller instance
108
     * @return array all available action IDs.
109
     */
110
    public function getActions($controller)
111
    {
112
        $actions = array_keys($controller->actions());
113
        $class = new \ReflectionClass($controller);
114
        foreach ($class->getMethods() as $method) {
115
            $name = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
116
            if ($name !== 'actions' && $method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) {
117
                $actions[] = Inflector::camel2id(substr($name, 6), '-', true);
118
            }
119
        }
120
        sort($actions);
121
122
        return array_unique($actions);
123
    }
124
125
    /**
126
     * Returns available commands of a specified module.
127
     * @param \yii\base\Module $module the module instance
128
     * @return array the available command names
129
     */
130
    protected function getModuleCommands($module)
131
    {
132
        $prefix = $module instanceof Application ? '' : $module->getUniqueId() . '/';
133
134
        $commands = [];
135
        foreach (array_keys($module->controllerMap) as $id) {
136
            $commands[] = $prefix . $id;
137
        }
138
139
        foreach ($module->getModules() as $id => $child) {
140
            if (($child = $module->getModule($id)) === null) {
141
                continue;
142
            }
143
            foreach ($this->getModuleCommands($child) as $command) {
144
                $commands[] = $command;
145
            }
146
        }
147
148
        $controllerPath = $module->getControllerPath();
149
        if (is_dir($controllerPath)) {
150
            $files = scandir($controllerPath);
151
            foreach ($files as $file) {
152
                if (!empty($file) && substr_compare($file, 'Controller.php', -14, 14) === 0) {
153
                    $controllerClass = $module->controllerNamespace . '\\' . substr(basename($file), 0, -4);
154
                    if ($this->validateControllerClass($controllerClass)) {
155
                        $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
156
                    }
157
                }
158
            }
159
        }
160
161
        return $commands;
162
    }
163
164
    /**
165
     * Validates if the given class is a valid console controller class.
166
     * @param string $controllerClass
167
     * @return bool
168
     */
169
    protected function validateControllerClass($controllerClass)
170
    {
171
        if (class_exists($controllerClass)) {
172
            $class = new \ReflectionClass($controllerClass);
173
            return !$class->isAbstract() && $class->isSubclassOf(Controller::class);
174
        } else {
175
            return false;
176
        }
177
    }
178
179
    /**
180
     * Displays all available commands.
181
     */
182
    protected function getDefaultHelp()
183
    {
184
        $commands = $this->getCommandDescriptions();
185
        $this->stdout($this->getDefaultHelpHeader());
186
        if (!empty($commands)) {
187
            $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
188
            $len = 0;
189
            foreach ($commands as $command => $description) {
190
                $result = Yii::$app->createController($command);
191
                if ($result !== false && $result[0] instanceof Controller) {
192
                    /** @var $controller Controller */
193
                    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...
194
                    $actions = $this->getActions($controller);
195
                    if (!empty($actions)) {
196
                        $prefix = $controller->getUniqueId();
197
                        foreach ($actions as $action) {
198
                            $string = $prefix . '/' . $action;
199
                            if ($action === $controller->defaultAction) {
200
                                $string .= ' (default)';
201
                            }
202
                            if (($l = strlen($string)) > $len) {
203
                                $len = $l;
204
                            }
205
                        }
206
                    }
207
                } elseif (($l = strlen($command)) > $len) {
208
                    $len = $l;
209
                }
210
            }
211
            foreach ($commands as $command => $description) {
212
                $this->stdout('- ' . $this->ansiFormat($command, Console::FG_YELLOW));
213
                $this->stdout(str_repeat(' ', $len + 4 - strlen($command)));
214
                $this->stdout(Console::wrapText($description, $len + 4 + 2), Console::BOLD);
215
                $this->stdout("\n");
216
217
                $result = Yii::$app->createController($command);
218
                if ($result !== false && $result[0] instanceof Controller) {
219
                    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...
220
                    $actions = $this->getActions($controller);
221
                    if (!empty($actions)) {
222
                        $prefix = $controller->getUniqueId();
223
                        foreach ($actions as $action) {
224
                            $string = '  ' . $prefix . '/' . $action;
225
                            $this->stdout('  ' . $this->ansiFormat($string, Console::FG_GREEN));
226
                            if ($action === $controller->defaultAction) {
227
                                $string .= ' (default)';
228
                                $this->stdout(' (default)', Console::FG_YELLOW);
229
                            }
230
                            $summary = $controller->getActionHelpSummary($controller->createAction($action));
231
                            if ($summary !== '') {
232
                                $this->stdout(str_repeat(' ', $len + 4 - strlen($string)));
233
                                $this->stdout(Console::wrapText($summary, $len + 4 + 2));
234
                            }
235
                            $this->stdout("\n");
236
                        }
237
                    }
238
                    $this->stdout("\n");
239
                }
240
            }
241
            $scriptName = $this->getScriptName();
242
            $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
243
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
244
                            . $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n");
245
        } else {
246
            $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
247
        }
248
    }
249
250
    /**
251
     * Displays the overall information of the command.
252
     * @param Controller $controller the controller instance
253
     */
254
    protected function getCommandHelp($controller)
255
    {
256
        $controller->color = $this->color;
257
258
        $this->stdout("\nDESCRIPTION\n", Console::BOLD);
259
        $comment = $controller->getHelp();
260
        if ($comment !== '') {
261
            $this->stdout("\n$comment\n\n");
262
        }
263
264
        $actions = $this->getActions($controller);
265
        if (!empty($actions)) {
266
            $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
267
            $prefix = $controller->getUniqueId();
268
269
            $maxlen = 5;
270
            foreach ($actions as $action) {
271
                $len = strlen($prefix . '/' . $action) + 2 + ($action === $controller->defaultAction ? 10 : 0);
272
                if ($maxlen < $len) {
273
                    $maxlen = $len;
274
                }
275
            }
276
            foreach ($actions as $action) {
277
                $this->stdout('- ' . $this->ansiFormat($prefix . '/' . $action, Console::FG_YELLOW));
278
                $len = strlen($prefix . '/' . $action) + 2;
279
                if ($action === $controller->defaultAction) {
280
                    $this->stdout(' (default)', Console::FG_GREEN);
281
                    $len += 10;
282
                }
283
                $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...
284
                if ($summary !== '') {
285
                    $this->stdout(str_repeat(' ', $maxlen - $len + 2) . Console::wrapText($summary, $maxlen + 2));
286
                }
287
                $this->stdout("\n");
288
            }
289
            $scriptName = $this->getScriptName();
290
            $this->stdout("\nTo see the detailed information about individual sub-commands, enter:\n");
291
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
292
                            . $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n");
293
        }
294
    }
295
296
    /**
297
     * Displays the detailed information of a command action.
298
     * @param Controller $controller the controller instance
299
     * @param string $actionID action ID
300
     * @throws Exception if the action does not exist
301
     */
302
    protected function getSubCommandHelp($controller, $actionID)
303
    {
304
        $action = $controller->createAction($actionID);
305
        if ($action === null) {
306
            $name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW);
307
            throw new Exception("No help for unknown sub-command \"$name\".");
308
        }
309
310
        $description = $controller->getActionHelp($action);
311
        if ($description !== '') {
312
            $this->stdout("\nDESCRIPTION\n", Console::BOLD);
313
            $this->stdout("\n$description\n\n");
314
        }
315
316
        $this->stdout("\nUSAGE\n\n", Console::BOLD);
317
        $scriptName = $this->getScriptName();
318
        if ($action->id === $controller->defaultAction) {
319
            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
320
        } else {
321
            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
322
        }
323
324
        $args = $controller->getActionArgsHelp($action);
325
        foreach ($args as $name => $arg) {
326
            if ($arg['required']) {
327
                $this->stdout(' <' . $name . '>', Console::FG_CYAN);
328
            } else {
329
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
330
            }
331
        }
332
333
        $options = $controller->getActionOptionsHelp($action);
334
        $options[\yii\console\Application::OPTION_APPCONFIG] = [
335
            'type' => 'string',
336
            'default' => null,
337
            'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
338
        ];
339
        ksort($options);
340
341
        if (!empty($options)) {
342
            $this->stdout(' [...options...]', Console::FG_RED);
343
        }
344
        $this->stdout("\n\n");
345
346
        if (!empty($args)) {
347
            foreach ($args as $name => $arg) {
348
                $this->stdout($this->formatOptionHelp(
349
                        '- ' . $this->ansiFormat($name, Console::FG_CYAN),
350
                        $arg['required'],
351
                        $arg['type'],
352
                        $arg['default'],
353
                        $arg['comment']) . "\n\n");
354
            }
355
        }
356
357
        if (!empty($options)) {
358
            $this->stdout("\nOPTIONS\n\n", Console::BOLD);
359
            foreach ($options as $name => $option) {
360
                $this->stdout($this->formatOptionHelp(
361
                        $this->ansiFormat('--' . $name . $this->formatOptionAliases($controller, $name), Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD),
362
                        !empty($option['required']),
363
                        $option['type'],
364
                        $option['default'],
365
                        $option['comment']) . "\n\n");
366
            }
367
        }
368
    }
369
370
    /**
371
     * Generates a well-formed string for an argument or option.
372
     * @param string $name the name of the argument or option
373
     * @param bool $required whether the argument is required
374
     * @param string $type the type of the option or argument
375
     * @param mixed $defaultValue the default value of the option or argument
376
     * @param string $comment comment about the option or argument
377
     * @return string the formatted string for the argument or option
378
     */
379
    protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
380
    {
381
        $comment = trim($comment);
382
        $type = trim($type);
383
        if (strncmp($type, 'bool', 4) === 0) {
384
            $type = 'boolean, 0 or 1';
385
        }
386
387
        if ($defaultValue !== null && !is_array($defaultValue)) {
388
            if ($type === null) {
389
                $type = gettype($defaultValue);
390
            }
391
            if (is_bool($defaultValue)) {
392
                // show as integer to avoid confusion
393
                $defaultValue = (int) $defaultValue;
394
            }
395
            if (is_string($defaultValue)) {
396
                $defaultValue = "'" . $defaultValue . "'";
397
            } else {
398
                $defaultValue = var_export($defaultValue, true);
399
            }
400
            $doc = "$type (defaults to $defaultValue)";
401
        } else {
402
            $doc = $type;
403
        }
404
405
        if ($doc === '') {
406
            $doc = $comment;
407
        } elseif ($comment !== '') {
408
            $doc .= "\n" . preg_replace('/^/m', '  ', $comment);
409
        }
410
411
        $name = $required ? "$name (required)" : $name;
412
413
        return $doc === '' ? $name : "$name: $doc";
414
    }
415
416
    /**
417
     * @param Controller $controller the controller instance
418
     * @param string $option the option name
419
     * @return string the formatted string for the alias argument or option
420
     * @since 2.0.8
421
     */
422
    protected function formatOptionAliases($controller, $option)
423
    {
424
        $aliases = $controller->optionAliases();
425
        foreach ($aliases as $name => $value) {
426
            if ($value === $option) {
427
                return ', -' . $name;
428
            }
429
        }
430
        return '';
431
    }
432
433
    /**
434
     * @return string the name of the cli script currently running.
435
     */
436
    protected function getScriptName()
437
    {
438
        return basename(Yii::$app->request->scriptFile);
439
    }
440
441
    /**
442
     * Return a default help header.
443
     * @return string default help header.
444
     * @since 2.0.11
445
     */
446
    protected function getDefaultHelpHeader()
447
    {
448
        return "\nThis is Yii version " . \Yii::getVersion() . ".\n";
449
    }
450
}
451