Completed
Push — master ( 062ebf...6e2b17 )
by Alexander
26:33 queued 48s
created

HelpController::getDefaultHelp()   B

Complexity

Conditions 9
Paths 25

Size

Total Lines 54
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 41
CRAP Score 9.0081

Importance

Changes 0
Metric Value
cc 9
eloc 42
nc 25
nop 0
dl 0
loc 54
ccs 41
cts 43
cp 0.9535
crap 9.0081
rs 7.6924
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 5
    public function actionIndex($command = null)
50
    {
51 5
        if ($command !== null) {
52 3
            $result = Yii::$app->createController($command);
53 3
            if ($result === false) {
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
54
                $name = $this->ansiFormat($command, Console::FG_YELLOW);
55
                throw new Exception("No help for unknown command \"$name\".");
56
            }
57
58 3
            list($controller, $actionID) = $result;
59
60 3
            $actions = $this->getActions($controller);
61 3
            if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
62 3
                $this->getSubCommandHelp($controller, $actionID);
63
            } else {
64 3
                $this->getCommandHelp($controller);
65
            }
66
        } else {
67 2
            $this->getDefaultHelp();
68
        }
69 5
    }
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 3
    public function actionList()
77
    {
78 3
        foreach ($this->getCommandDescriptions() as $command => $description) {
79 3
            $result = Yii::$app->createController($command);
80
            /** @var $controller Controller */
81 3
            list($controller, $actionID) = $result;
82 3
            $actions = $this->getActions($controller);
83 3
            $prefix = $controller->getUniqueId();
84 3
            if ($controller->createAction($controller->defaultAction) !== null) {
85 3
                $this->stdout("$prefix\n");
86
            }
87 3
            foreach ($actions as $action) {
88 3
                $this->stdout("$prefix/$action\n");
89
            }
90
        }
91 3
    }
92
93
    /**
94
     * List all available options for the $action in machine readable format.
95
     * This is used for shell completion.
96
     *
97
     * @param string $action route to action
98
     * @since 2.0.11
99
     */
100 1
    public function actionListActionOptions($action)
101
    {
102 1
        $result = Yii::$app->createController($action);
103
104 1
        if ($result === false || !($result[0] instanceof Controller)) {
105
            return;
106
        }
107
108
        /** @var Controller $controller */
109 1
        list($controller, $actionID) = $result;
110 1
        $action = $controller->createAction($actionID);
111 1
        if ($action === null) {
112
            return;
113
        }
114
115 1
        foreach ($controller->getActionArgsHelp($action) as $argument => $help) {
116 1
            $description = preg_replace("~\R~", '', addcslashes($help['comment'], ':')) ?: $argument;
117 1
            $this->stdout($argument . ':' . $description . "\n");
118
        }
119
120 1
        $this->stdout("\n");
121 1
        foreach ($controller->getActionOptionsHelp($action) as $argument => $help) {
122 1
            $description = preg_replace("~\R~", '', addcslashes($help['comment'], ':'));
123 1
            $this->stdout('--' . $argument . ($description ? ':' . $description : '') . "\n");
124
        }
125 1
    }
126
127
    /**
128
     * Displays usage information for $action.
129
     *
130
     * @param string $action route to action
131
     * @since 2.0.11
132
     */
133 1
    public function actionUsage($action)
134
    {
135 1
        $result = Yii::$app->createController($action);
136
137 1
        if ($result === false || !($result[0] instanceof Controller)) {
138
            return;
139
        }
140
141
        /** @var Controller $controller */
142 1
        list($controller, $actionID) = $result;
143 1
        $action = $controller->createAction($actionID);
144 1
        if ($action === null) {
145
            return;
146
        }
147
148 1
        $scriptName = $this->getScriptName();
149 1
        if ($action->id === $controller->defaultAction) {
150
            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
151
        } else {
152 1
            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
153
        }
154
155 1
        foreach ($controller->getActionArgsHelp($action) as $name => $arg) {
156 1
            if ($arg['required']) {
157 1
                $this->stdout(' <' . $name . '>', Console::FG_CYAN);
158
            } else {
159 1
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
160
            }
161
        }
162
163 1
        $this->stdout("\n");
164 1
    }
165
166
    /**
167
     * Returns all available command names.
168
     * @return array all available command names
169
     */
170 21
    public function getCommands()
171
    {
172 21
        $commands = $this->getModuleCommands(Yii::$app);
173 21
        sort($commands);
174 21
        return array_filter(array_unique($commands), function ($command) {
175 21
            $result = Yii::$app->createController($command);
176 21
            if ($result === false || !$result[0] instanceof Controller) {
177
                return false;
178
            }
179 21
            list($controller, $actionID) = $result;
180 21
            $actions = $this->getActions($controller);
181 21
            return $actions !== [];
182 21
        });
183
    }
184
185
    /**
186
     * Returns an array of commands an their descriptions.
187
     * @return array all available commands as keys and their description as values.
188
     */
189 5
    protected function getCommandDescriptions()
190
    {
191 5
        $descriptions = [];
192 5
        foreach ($this->getCommands() as $command) {
193 5
            $result = Yii::$app->createController($command);
194
            /** @var Controller $controller */
195 5
            list($controller, $actionID) = $result;
196 5
            $descriptions[$command] = $controller->getHelpSummary();
197
        }
198
199 5
        return $descriptions;
200
    }
201
202
    /**
203
     * Returns all available actions of the specified controller.
204
     * @param Controller $controller the controller instance
205
     * @return array all available action IDs.
206
     */
207 24
    public function getActions($controller)
208
    {
209 24
        $actions = array_keys($controller->actions());
210 24
        $class = new \ReflectionClass($controller);
211 24
        foreach ($class->getMethods() as $method) {
212 24
            $name = $method->getName();
213 24
            if ($name !== 'actions' && $method->isPublic() && !$method->isStatic() && strncmp($name, 'action', 6) === 0) {
214 24
                $actions[] = $this->camel2id(substr($name, 6));
215
            }
216
        }
217 24
        sort($actions);
218
219 24
        return array_unique($actions);
220
    }
221
222
    /**
223
     * Returns available commands of a specified module.
224
     * @param \yii\base\Module $module the module instance
225
     * @return array the available command names
226
     */
227 21
    protected function getModuleCommands($module)
228
    {
229 21
        $prefix = $module instanceof Application ? '' : $module->getUniqueId() . '/';
230
231 21
        $commands = [];
232 21
        foreach (array_keys($module->controllerMap) as $id) {
233 21
            $commands[] = $prefix . $id;
234
        }
235
236 21
        foreach ($module->getModules() as $id => $child) {
237 1
            if (($child = $module->getModule($id)) === null) {
238
                continue;
239
            }
240 1
            foreach ($this->getModuleCommands($child) as $command) {
241 1
                $commands[] = $command;
242
            }
243
        }
244
245 21
        $controllerPath = $module->getControllerPath();
246 21
        if (is_dir($controllerPath)) {
247 3
            $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($controllerPath, \RecursiveDirectoryIterator::KEY_AS_PATHNAME));
248 3
            $iterator = new \RegexIterator($iterator, '/.*Controller\.php$/', \RecursiveRegexIterator::GET_MATCH);
249 3
            foreach ($iterator as $matches) {
250 3
                $file = $matches[0];
251 3
                $relativePath = str_replace($controllerPath, '', $file);
252 3
                $class = strtr($relativePath, [
253 3
                    '/' => '\\',
254
                    '.php' => '',
255
                ]);
256 3
                $controllerClass = $module->controllerNamespace . $class;
257 3
                if ($this->validateControllerClass($controllerClass)) {
258 3
                    $dir = ltrim(pathinfo($relativePath, PATHINFO_DIRNAME), '\\/');
259
260 3
                    $command = Inflector::camel2id(substr(basename($file), 0, -14), '-', true);
261 3
                    if (!empty($dir)) {
262 1
                        $command = $dir . '/' . $command;
263
                    }
264 3
                    $commands[] = $prefix . $command;
265
                }
266
            }
267
        }
268
269 21
        return $commands;
270
    }
271
272
    /**
273
     * Validates if the given class is a valid console controller class.
274
     * @param string $controllerClass
275
     * @return bool
276
     */
277 3
    protected function validateControllerClass($controllerClass)
278
    {
279 3
        if (class_exists($controllerClass)) {
280 3
            $class = new \ReflectionClass($controllerClass);
281 3
            return !$class->isAbstract() && $class->isSubclassOf('yii\console\Controller');
282
        }
283
284
        return false;
285
    }
286
287
    /**
288
     * Displays all available commands.
289
     */
290 2
    protected function getDefaultHelp()
291
    {
292 2
        $commands = $this->getCommandDescriptions();
293 2
        $this->stdout($this->getDefaultHelpHeader());
294 2
        if (empty($commands)) {
295
            $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
296
            return;
297
        }
298
299 2
        $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
300 2
        $maxLength = 0;
301 2
        foreach ($commands as $command => $description) {
302 2
            $result = Yii::$app->createController($command);
303
            /** @var $controller Controller */
304 2
            list($controller, $actionID) = $result;
305 2
            $actions = $this->getActions($controller);
306 2
            $prefix = $controller->getUniqueId();
307 2
            foreach ($actions as $action) {
308 2
                $string = $prefix . '/' . $action;
309 2
                if ($action === $controller->defaultAction) {
310 2
                    $string .= ' (default)';
311
                }
312 2
                $maxLength = max($maxLength, strlen($string));
313
            }
314
        }
315 2
        foreach ($commands as $command => $description) {
316 2
            $result = Yii::$app->createController($command);
317 2
            list($controller, $actionID) = $result;
318 2
            $actions = $this->getActions($controller);
319 2
            $this->stdout('- ' . $this->ansiFormat($command, Console::FG_YELLOW));
320 2
            $this->stdout(str_repeat(' ', $maxLength + 4 - strlen($command)));
321 2
            $this->stdout(Console::wrapText($description, $maxLength + 4 + 2), Console::BOLD);
322 2
            $this->stdout("\n");
323 2
            $prefix = $controller->getUniqueId();
324 2
            foreach ($actions as $action) {
325 2
                $string = '  ' . $prefix . '/' . $action;
326 2
                $this->stdout('  ' . $this->ansiFormat($string, Console::FG_GREEN));
327 2
                if ($action === $controller->defaultAction) {
328 2
                    $string .= ' (default)';
329 2
                    $this->stdout(' (default)', Console::FG_YELLOW);
330
                }
331 2
                $summary = $controller->getActionHelpSummary($controller->createAction($action));
332 2
                if ($summary !== '') {
333 2
                    $this->stdout(str_repeat(' ', $maxLength + 4 - strlen($string)));
334 2
                    $this->stdout(Console::wrapText($summary, $maxLength + 4 + 2));
335
                }
336 2
                $this->stdout("\n");
337
            }
338 2
            $this->stdout("\n");
339
        }
340 2
        $scriptName = $this->getScriptName();
341 2
        $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
342 2
        $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
343 2
            . $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n");
344 2
    }
345
346
    /**
347
     * Displays the overall information of the command.
348
     * @param Controller $controller the controller instance
349
     */
350
    protected function getCommandHelp($controller)
351
    {
352
        $controller->color = $this->color;
353
354
        $this->stdout("\nDESCRIPTION\n", Console::BOLD);
355
        $comment = $controller->getHelp();
356
        if ($comment !== '') {
357
            $this->stdout("\n$comment\n\n");
358
        }
359
360
        $actions = $this->getActions($controller);
361
        if (!empty($actions)) {
362
            $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
363
            $prefix = $controller->getUniqueId();
364
365
            $maxlen = 5;
366
            foreach ($actions as $action) {
367
                $len = strlen($prefix . '/' . $action) + 2 + ($action === $controller->defaultAction ? 10 : 0);
368
                $maxlen = max($maxlen, $len);
369
            }
370
            foreach ($actions as $action) {
371
                $this->stdout('- ' . $this->ansiFormat($prefix . '/' . $action, Console::FG_YELLOW));
372
                $len = strlen($prefix . '/' . $action) + 2;
373
                if ($action === $controller->defaultAction) {
374
                    $this->stdout(' (default)', Console::FG_GREEN);
375
                    $len += 10;
376
                }
377
                $summary = $controller->getActionHelpSummary($controller->createAction($action));
378
                if ($summary !== '') {
379
                    $this->stdout(str_repeat(' ', $maxlen - $len + 2) . Console::wrapText($summary, $maxlen + 2));
380
                }
381
                $this->stdout("\n");
382
            }
383
            $scriptName = $this->getScriptName();
384
            $this->stdout("\nTo see the detailed information about individual sub-commands, enter:\n");
385
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
386
                . $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n");
387
        }
388
    }
389
390
    /**
391
     * Displays the detailed information of a command action.
392
     * @param Controller $controller the controller instance
393
     * @param string $actionID action ID
394
     * @throws Exception if the action does not exist
395
     */
396 3
    protected function getSubCommandHelp($controller, $actionID)
397
    {
398 3
        $action = $controller->createAction($actionID);
399 3
        if ($action === null) {
400
            $name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW);
401
            throw new Exception("No help for unknown sub-command \"$name\".");
402
        }
403
404 3
        $description = $controller->getActionHelp($action);
405 3
        if ($description !== '') {
406 2
            $this->stdout("\nDESCRIPTION\n", Console::BOLD);
407 2
            $this->stdout("\n$description\n\n");
408
        }
409
410 3
        $this->stdout("\nUSAGE\n\n", Console::BOLD);
411 3
        $scriptName = $this->getScriptName();
412 3
        if ($action->id === $controller->defaultAction) {
413 2
            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
414
        } else {
415 1
            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
416
        }
417
418 3
        $args = $controller->getActionArgsHelp($action);
419 3
        foreach ($args as $name => $arg) {
420 3
            if ($arg['required']) {
421 1
                $this->stdout(' <' . $name . '>', Console::FG_CYAN);
422
            } else {
423 3
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
424
            }
425
        }
426
427 3
        $options = $controller->getActionOptionsHelp($action);
428 3
        $options[\yii\console\Application::OPTION_APPCONFIG] = [
429
            'type' => 'string',
430
            'default' => null,
431
            'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
432
        ];
433 3
        ksort($options);
434
435 3
        if (!empty($options)) {
436 3
            $this->stdout(' [...options...]', Console::FG_RED);
437
        }
438 3
        $this->stdout("\n\n");
439
440 3
        if (!empty($args)) {
441 3
            foreach ($args as $name => $arg) {
442 3
                $this->stdout($this->formatOptionHelp(
443 3
                        '- ' . $this->ansiFormat($name, Console::FG_CYAN),
444 3
                        $arg['required'],
445 3
                        $arg['type'],
446 3
                        $arg['default'],
447 3
                        $arg['comment']) . "\n\n");
448
            }
449
        }
450
451 3
        if (!empty($options)) {
452 3
            $this->stdout("\nOPTIONS\n\n", Console::BOLD);
453 3
            foreach ($options as $name => $option) {
454 3
                $this->stdout($this->formatOptionHelp(
455 3
                        $this->ansiFormat('--' . $name . $this->formatOptionAliases($controller, $name),
456 3
                            Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD),
457 3
                        !empty($option['required']),
458 3
                        $option['type'],
459 3
                        $option['default'],
460 3
                        $option['comment']) . "\n\n");
461
            }
462
        }
463 3
    }
464
465
    /**
466
     * Generates a well-formed string for an argument or option.
467
     * @param string $name the name of the argument or option
468
     * @param bool $required whether the argument is required
469
     * @param string $type the type of the option or argument
470
     * @param mixed $defaultValue the default value of the option or argument
471
     * @param string $comment comment about the option or argument
472
     * @return string the formatted string for the argument or option
473
     */
474 3
    protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
475
    {
476 3
        $comment = trim($comment);
477 3
        $type = trim($type);
478 3
        if (strncmp($type, 'bool', 4) === 0) {
479 3
            $type = 'boolean, 0 or 1';
480
        }
481
482 3
        if ($defaultValue !== null && !is_array($defaultValue)) {
483 3
            if ($type === null) {
0 ignored issues
show
introduced by
The condition $type === null is always false.
Loading history...
484
                $type = gettype($defaultValue);
485
            }
486 3
            if (is_bool($defaultValue)) {
487
                // show as integer to avoid confusion
488 3
                $defaultValue = (int)$defaultValue;
489
            }
490 3
            if (is_string($defaultValue)) {
491 2
                $defaultValue = "'" . $defaultValue . "'";
492
            } else {
493 3
                $defaultValue = var_export($defaultValue, true);
494
            }
495 3
            $doc = "$type (defaults to $defaultValue)";
496
        } else {
497 3
            $doc = $type;
498
        }
499
500 3
        if ($doc === '') {
501 1
            $doc = $comment;
502 3
        } elseif ($comment !== '') {
503 3
            $doc .= "\n" . preg_replace('/^/m', '  ', $comment);
504
        }
505
506 3
        $name = $required ? "$name (required)" : $name;
507
508 3
        return $doc === '' ? $name : "$name: $doc";
509
    }
510
511
    /**
512
     * @param Controller $controller the controller instance
513
     * @param string $option the option name
514
     * @return string the formatted string for the alias argument or option
515
     * @since 2.0.8
516
     */
517 3
    protected function formatOptionAliases($controller, $option)
518
    {
519 3
        foreach ($controller->optionAliases() as $name => $value) {
520 3
            if (Inflector::camel2id($value, '-', true) === $option) {
521 3
                return ', -' . $name;
522
            }
523
        }
524
525 3
        return '';
526
    }
527
528
    /**
529
     * @return string the name of the cli script currently running.
530
     */
531 6
    protected function getScriptName()
532
    {
533 6
        return basename(Yii::$app->request->scriptFile);
534
    }
535
536
    /**
537
     * Return a default help header.
538
     * @return string default help header.
539
     * @since 2.0.11
540
     */
541 2
    protected function getDefaultHelpHeader()
542
    {
543 2
        return "\nThis is Yii version " . \Yii::getVersion() . ".\n";
544
    }
545
546
    /**
547
     * Converts a CamelCase action name into an ID in lowercase.
548
     * Words in the ID are concatenated using the specified character '-'.
549
     * For example, 'CreateUser' will be converted to 'create-user'.
550
     * @param string $name the string to be converted
551
     * @return string the resulting ID
552
     */
553 24
    private function camel2id($name)
554
    {
555 24
        return mb_strtolower(trim(preg_replace('/\p{Lu}/u', '-\0', $name), '-'), 'UTF-8');
556
    }
557
}
558