HelpController.php 13.1 KB
Newer Older
Alexander Makarov committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
Alexander Makarov committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

8
namespace yii\console\controllers;
Alexander Makarov committed
9

10
use Yii;
Qiang Xue committed
11 12
use yii\base\Application;
use yii\console\Controller;
Qiang Xue committed
13
use yii\console\Exception;
14
use yii\helpers\Console;
Alexander Makarov committed
15
use yii\helpers\Inflector;
Qiang Xue committed
16

Alexander Makarov committed
17
/**
18
 * Provides help information about console commands.
Alexander Makarov committed
19
 *
Qiang Xue committed
20 21 22
 * This command displays the available command list in
 * the application or the detailed instructions about using
 * a specific command.
Alexander Makarov committed
23
 *
Qiang Xue committed
24
 * This command can be used as follows on command line:
Qiang Xue committed
25 26
 *
 * ~~~
27
 * yii help [command name]
Qiang Xue committed
28 29
 * ~~~
 *
Qiang Xue committed
30 31
 * In the above, if the command name is not provided, all
 * available commands will be displayed.
Alexander Makarov committed
32
 *
33
 * @property array $commands All available command names. This property is read-only.
34
 *
Alexander Makarov committed
35 36 37
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
Qiang Xue committed
38
class HelpController extends Controller
Alexander Makarov committed
39
{
40 41
    /**
     * Displays available commands or the detailed information
42
     * about a particular command.
43
     *
44 45 46
     * @param string $command The name of the command to show help about.
     * If not provided, all available commands will be displayed.
     * @return integer the exit status
47 48 49 50 51 52 53
     * @throws Exception if the command for help is unknown
     */
    public function actionIndex($command = null)
    {
        if ($command !== null) {
            $result = Yii::$app->createController($command);
            if ($result === false) {
54
                $name = $this->ansiFormat($command, Console::FG_YELLOW);
55
                throw new Exception("No help for unknown command \"$name\".");
56 57 58 59 60 61
            }

            list($controller, $actionID) = $result;

            $actions = $this->getActions($controller);
            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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
        }
    }

    /**
     * Returns all available command names.
     * @return array all available command names
     */
    public function getCommands()
    {
        $commands = $this->getModuleCommands(Yii::$app);
        sort($commands);
        return array_unique($commands);
    }

    /**
     * Returns an array of commands an their descriptions.
     * @return array all available commands as keys and their description as values.
     */
    protected function getCommandDescriptions()
    {
        $descriptions = [];
        foreach ($this->getCommands() as $command) {
            $description = '';

            $result = Yii::$app->createController($command);
            if ($result !== false) {
                list($controller, $actionID) = $result;
95 96
                /** @var Controller $controller */
                $description = $controller->getHelpSummary();
97 98 99 100 101 102 103 104 105 106
            }

            $descriptions[$command] = $description;
        }

        return $descriptions;
    }

    /**
     * Returns all available actions of the specified controller.
107 108
     * @param Controller $controller the controller instance
     * @return array all available action IDs.
109 110 111 112 113 114 115 116
     */
    public function getActions($controller)
    {
        $actions = array_keys($controller->actions());
        $class = new \ReflectionClass($controller);
        foreach ($class->getMethods() as $method) {
            $name = $method->getName();
            if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
117
                $actions[] = Inflector::camel2id(substr($name, 6), '-', true);
118 119 120 121 122 123 124 125 126
            }
        }
        sort($actions);

        return array_unique($actions);
    }

    /**
     * Returns available commands of a specified module.
127 128
     * @param \yii\base\Module $module the module instance
     * @return array the available command names
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
     */
    protected function getModuleCommands($module)
    {
        $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';

        $commands = [];
        foreach (array_keys($module->controllerMap) as $id) {
            $commands[] = $prefix . $id;
        }

        foreach ($module->getModules() as $id => $child) {
            if (($child = $module->getModule($id)) === null) {
                continue;
            }
            foreach ($this->getModuleCommands($child) as $command) {
                $commands[] = $command;
            }
        }

        $controllerPath = $module->getControllerPath();
        if (is_dir($controllerPath)) {
            $files = scandir($controllerPath);
            foreach ($files as $file) {
152
                if (!empty($file) && substr_compare($file, 'Controller.php', -14, 14) === 0) {
153 154 155 156
                    $controllerClass = $module->controllerNamespace . '\\' . substr(basename($file), 0, -4);
                    if ($this->validateControllerClass($controllerClass)) {
                        $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
                    }
157 158 159 160 161 162 163
                }
            }
        }

        return $commands;
    }

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    /**
     * Validates if the given class is a valid console controller class.
     * @param string $controllerClass
     * @return bool
     */
    protected function validateControllerClass($controllerClass)
    {
        if (class_exists($controllerClass)) {
            $class = new \ReflectionClass($controllerClass);
            return !$class->isAbstract() && $class->isSubclassOf('yii\console\Controller');
        } else {
            return false;
        }
    }

179 180 181
    /**
     * Displays all available commands.
     */
182
    protected function getDefaultHelp()
183 184
    {
        $commands = $this->getCommandDescriptions();
185
        $this->stdout("\nThis is Yii version " . \Yii::getVersion() . ".\n");
186 187 188 189 190 191 192 193 194
        if (!empty($commands)) {
            $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
            $len = 0;
            foreach ($commands as $command => $description) {
                if (($l = strlen($command)) > $len) {
                    $len = $l;
                }
            }
            foreach ($commands as $command => $description) {
195 196 197
                $this->stdout("- " . $this->ansiFormat($command, Console::FG_YELLOW));
                $this->stdout(str_repeat(' ', $len + 3 - strlen($command)) . $description);
                $this->stdout("\n");
198 199 200
            }
            $scriptName = $this->getScriptName();
            $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
201 202
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
                            . $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n");
203 204 205 206 207 208 209 210 211
        } else {
            $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
        }
    }

    /**
     * Displays the overall information of the command.
     * @param Controller $controller the controller instance
     */
212
    protected function getCommandHelp($controller)
213
    {
214
        $controller->color = $this->color;
215

Carsten Brandt committed
216 217
        $this->stdout("\nDESCRIPTION\n", Console::BOLD);
        $comment = $controller->getHelp();
218
        if ($comment !== '') {
Carsten Brandt committed
219
            $this->stdout("\n$comment\n\n");
220 221 222 223 224 225 226
        }

        $actions = $this->getActions($controller);
        if (!empty($actions)) {
            $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
            $prefix = $controller->getUniqueId();
            foreach ($actions as $action) {
227
                $this->stdout('- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW));
228 229 230
                if ($action === $controller->defaultAction) {
                    $this->stdout(' (default)', Console::FG_GREEN);
                }
231
                $summary = $controller->getActionHelpSummary($controller->createAction($action));
232
                if ($summary !== '') {
233
                    $this->stdout(': ' . $summary);
234
                }
235
                $this->stdout("\n");
236 237
            }
            $scriptName = $this->getScriptName();
238 239 240
            $this->stdout("\nTo see the detailed information about individual sub-commands, enter:\n");
            $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
                            . $this->ansiFormat('<sub-command>', Console::FG_CYAN) . "\n\n");
241 242 243 244 245
        }
    }

    /**
     * Displays the detailed information of a command action.
246 247 248
     * @param Controller $controller the controller instance
     * @param string $actionID action ID
     * @throws Exception if the action does not exist
249
     */
250
    protected function getSubCommandHelp($controller, $actionID)
251 252 253
    {
        $action = $controller->createAction($actionID);
        if ($action === null) {
254
            $name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW);
255
            throw new Exception("No help for unknown sub-command \"$name\".");
256 257
        }

258 259
        $description = $controller->getActionHelp($action);
        if ($description != '') {
260
            $this->stdout("\nDESCRIPTION\n", Console::BOLD);
Carsten Brandt committed
261
            $this->stdout("\n$description\n\n");
262 263 264 265 266
        }

        $this->stdout("\nUSAGE\n\n", Console::BOLD);
        $scriptName = $this->getScriptName();
        if ($action->id === $controller->defaultAction) {
267
            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
268
        } else {
269
            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
270
        }
271

272 273 274 275 276 277 278
        $args = $controller->getActionArgsHelp($action);
        foreach ($args as $name => $arg) {
            if ($arg['required']) {
                $this->stdout(' <' . $name . '>', Console::FG_CYAN);
            } else {
                $this->stdout(' [' . $name . ']', Console::FG_CYAN);
            }
279 280
        }

281 282 283 284 285 286 287 288
        $options = $controller->getActionOptionsHelp($action);
        $options[\yii\console\Application::OPTION_APPCONFIG] = [
            'type' => 'string',
            'default' => null,
            'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.",
        ];
        ksort($options);

289 290 291
        if (!empty($options)) {
            $this->stdout(' [...options...]', Console::FG_RED);
        }
292
        $this->stdout("\n\n");
293

294 295
        if (!empty($args)) {
            foreach ($args as $name => $arg) {
296
                $this->stdout($this->formatOptionHelp(
Qiang Xue committed
297 298 299 300
                        '- ' . $this->ansiFormat($name, Console::FG_CYAN),
                        $arg['required'],
                        $arg['type'],
                        $arg['default'],
301
                        $arg['comment']) . "\n\n");
302
            }
303 304 305 306
        }

        if (!empty($options)) {
            $this->stdout("\nOPTIONS\n\n", Console::BOLD);
307
            foreach ($options as $name => $option) {
308
                $this->stdout($this->formatOptionHelp(
Qiang Xue committed
309 310 311 312
                        $this->ansiFormat('--' . $name, Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD),
                        !empty($option['required']),
                        $option['type'],
                        $option['default'],
313
                        $option['comment']) . "\n\n");
314 315 316 317 318 319
            }
        }
    }

    /**
     * Generates a well-formed string for an argument or option.
320 321 322 323 324 325
     * @param string $name the name of the argument or option
     * @param boolean $required whether the argument is required
     * @param string $type the type of the option or argument
     * @param mixed $defaultValue the default value of the option or argument
     * @param string $comment comment about the option or argument
     * @return string the formatted string for the argument or option
326 327 328 329
     */
    protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
    {
        $comment = trim($comment);
330 331 332 333
        $type = trim($type);
        if (strncmp($type, 'bool', 4) === 0) {
            $type = 'boolean, 0 or 1';
        }
334 335 336 337 338 339 340 341 342

        if ($defaultValue !== null && !is_array($defaultValue)) {
            if ($type === null) {
                $type = gettype($defaultValue);
            }
            if (is_bool($defaultValue)) {
                // show as integer to avoid confusion
                $defaultValue = (int) $defaultValue;
            }
Qiang Xue committed
343 344 345 346 347 348
            if (is_string($defaultValue)) {
                $defaultValue = "'" . $defaultValue . "'";
            } else {
                $defaultValue = var_export($defaultValue, true);
            }
            $doc = "$type (defaults to " . $defaultValue . ")";
349
        } else {
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
            $doc = $type;
        }

        if ($doc === '') {
            $doc = $comment;
        } elseif ($comment !== '') {
            $doc .= "\n" . preg_replace("/^/m", "  ", $comment);
        }

        $name = $required ? "$name (required)" : $name;

        return $doc === '' ? $name : "$name: $doc";
    }

    /**
     * @return string the name of the cli script currently running.
     */
    protected function getScriptName()
    {
        return basename(Yii::$app->request->scriptFile);
    }
Zander Baldwin committed
371
}