GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

HelpController   F
last analyzed

Complexity

Total Complexity 95

Size/Duplication

Total Lines 523
Duplicated Lines 0 %

Test Coverage

Coverage 83.7%

Importance

Changes 0
Metric Value
eloc 258
dl 0
loc 523
ccs 226
cts 270
cp 0.837
rs 2
c 0
b 0
f 0
wmc 95

17 Methods

Rating   Name   Duplication   Size   Complexity  
A validateControllerClass() 0 8 3
A getCommandDescriptions() 0 11 2
A actionList() 0 13 4
A getActions() 0 13 6
A actionIndex() 0 19 6
A getCommands() 0 12 3
B actionListActionOptions() 0 24 8
B getDefaultHelp() 0 54 9
B getCommandHelp() 0 37 8
B actionUsage() 0 31 7
A getScriptName() 0 3 1
A getDefaultHelpHeader() 0 3 1
C getSubCommandHelp() 0 70 12
C formatOptionHelp() 0 35 11
B getModuleCommands() 0 43 10
A camel2id() 0 3 1
A formatOptionAliases() 0 9 3

How to fix   Complexity   

Complex Class

Complex classes like HelpController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HelpController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://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-read array $commands All available command names.
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|null $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) {
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
    }
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
    }
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
    }
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
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
160
            }
161
        }
162
163 1
        $this->stdout("\n");
164
    }
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 3
                    '.php' => '',
255 3
                ]);
256 3
                $controllerClass = $module->controllerNamespace . $class;
257 3
                if ($this->validateControllerClass($controllerClass)) {
258 3
                    $dir = ltrim(pathinfo($relativePath, PATHINFO_DIRNAME), '\\/');
0 ignored issues
show
Bug introduced by
It seems like pathinfo($relativePath, ...llers\PATHINFO_DIRNAME) can also be of type array; however, parameter $string of ltrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

258
                    $dir = ltrim(/** @scrutinizer ignore-type */ pathinfo($relativePath, PATHINFO_DIRNAME), '\\/');
Loading history...
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
    }
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 3
            'type' => 'string',
430 3
            'default' => null,
431 3
            'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
432 3
        ];
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']
448 3
                ) . "\n\n");
449
            }
450
        }
451
452 3
        if (!empty($options)) {
453 3
            $this->stdout("\nOPTIONS\n\n", Console::BOLD);
454 3
            foreach ($options as $name => $option) {
455 3
                $this->stdout($this->formatOptionHelp(
456 3
                    $this->ansiFormat(
457 3
                        '--' . $name . $this->formatOptionAliases($controller, $name),
458 3
                        Console::FG_RED,
459 3
                        empty($option['required']) ? Console::FG_RED : Console::BOLD
460 3
                    ),
461 3
                    !empty($option['required']),
462 3
                    $option['type'],
463 3
                    $option['default'],
464 3
                    $option['comment']
465 3
                ) . "\n\n");
466
            }
467
        }
468
    }
469
470
    /**
471
     * Generates a well-formed string for an argument or option.
472
     * @param string $name the name of the argument or option
473
     * @param bool $required whether the argument is required
474
     * @param string $type the type of the option or argument
475
     * @param mixed $defaultValue the default value of the option or argument
476
     * @param string $comment comment about the option or argument
477
     * @return string the formatted string for the argument or option
478
     */
479 3
    protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
480
    {
481 3
        $comment = trim((string)$comment);
482 3
        $type = trim((string)$type);
483 3
        if (strncmp($type, 'bool', 4) === 0) {
484 3
            $type = 'boolean, 0 or 1';
485
        }
486
487 3
        if ($defaultValue !== null && !is_array($defaultValue)) {
488 3
            if ($type === null) {
0 ignored issues
show
introduced by
The condition $type === null is always false.
Loading history...
489
                $type = gettype($defaultValue);
490
            }
491 3
            if (is_bool($defaultValue)) {
492
                // show as integer to avoid confusion
493 3
                $defaultValue = (int) $defaultValue;
494
            }
495 3
            if (is_string($defaultValue)) {
496 2
                $defaultValue = "'" . $defaultValue . "'";
497
            } else {
498 3
                $defaultValue = var_export($defaultValue, true);
499
            }
500 3
            $doc = "$type (defaults to $defaultValue)";
501
        } else {
502 3
            $doc = $type;
503
        }
504
505 3
        if ($doc === '') {
506 1
            $doc = $comment;
507 3
        } elseif ($comment !== '') {
508 3
            $doc .= "\n" . preg_replace('/^/m', '  ', $comment);
509
        }
510
511 3
        $name = $required ? "$name (required)" : $name;
512
513 3
        return $doc === '' ? $name : "$name: $doc";
514
    }
515
516
    /**
517
     * @param Controller $controller the controller instance
518
     * @param string $option the option name
519
     * @return string the formatted string for the alias argument or option
520
     * @since 2.0.8
521
     */
522 3
    protected function formatOptionAliases($controller, $option)
523
    {
524 3
        foreach ($controller->optionAliases() as $name => $value) {
525 3
            if (Inflector::camel2id($value, '-', true) === $option) {
526 3
                return ', -' . $name;
527
            }
528
        }
529
530 3
        return '';
531
    }
532
533
    /**
534
     * @return string the name of the cli script currently running.
535
     */
536 6
    protected function getScriptName()
537
    {
538 6
        return basename(Yii::$app->request->scriptFile);
539
    }
540
541
    /**
542
     * Return a default help header.
543
     * @return string default help header.
544
     * @since 2.0.11
545
     */
546 2
    protected function getDefaultHelpHeader()
547
    {
548 2
        return "\nThis is Yii version " . \Yii::getVersion() . ".\n";
549
    }
550
551
    /**
552
     * Converts a CamelCase action name into an ID in lowercase.
553
     * Words in the ID are concatenated using the specified character '-'.
554
     * For example, 'CreateUser' will be converted to 'create-user'.
555
     * @param string $name the string to be converted
556
     * @return string the resulting ID
557
     */
558 24
    private function camel2id($name)
559
    {
560 24
        return mb_strtolower(trim(preg_replace('/\p{Lu}/u', '-\0', $name), '-'), 'UTF-8');
561
    }
562
}
563