| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 | <?php/** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */namespace yii\console\controllers;use Yii;use yii\base\Application;use yii\console\Controller;use yii\console\Exception;use yii\helpers\Console;use yii\helpers\Inflector;/** * Provides help information about console commands. * * This command displays the available command list in * the application or the detailed instructions about using * a specific command. * * This command can be used as follows on command line: * * ``` * yii help [command name] * ``` * * In the above, if the command name is not provided, all * available commands will be displayed. * * @property array $commands All available command names. This property is read-only. * * @author Qiang Xue <qiang.xue@gmail.com> * @since 2.0 */class HelpController extends Controller{    /**     * Displays available commands or the detailed information     * about a particular command.     *     * @param string $command The name of the command to show help about.     * If not provided, all available commands will be displayed.     * @return int the exit status     * @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) {                $name = $this->ansiFormat($command, Console::FG_YELLOW);                throw new Exception("No help for unknown command \"$name\".");            }            list($controller, $actionID) = $result;            $actions = $this->getActions($controller);            if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {                $this->getSubCommandHelp($controller, $actionID);            } else {                $this->getCommandHelp($controller);            }        } else {            $this->getDefaultHelp();        }    }    /**     * List all available controllers and actions in machine readable format.     * This is used for shell completion.     * @since 2.0.11     */    public function actionList()    {        foreach ($this->getCommandDescriptions() as $command => $description) {            $result = Yii::$app->createController($command);            /** @var $controller Controller */            list($controller, $actionID) = $result;            $actions = $this->getActions($controller);            $prefix = $controller->getUniqueId();            if ($controller->createAction($controller->defaultAction) !== null) {                $this->stdout("$prefix\n");            }            foreach ($actions as $action) {                $this->stdout("$prefix/$action\n");            }        }    }    /**     * List all available options for the $action in machine readable format.     * This is used for shell completion.     *     * @param string $action route to action     * @since 2.0.11     */    public function actionListActionOptions($action)    {        $result = Yii::$app->createController($action);        if ($result === false || !($result[0] instanceof Controller)) {            return;        }        /** @var Controller $controller */        list($controller, $actionID) = $result;        $action = $controller->createAction($actionID);        if ($action === null) {            return;        }        foreach ($controller->getActionArgsHelp($action) as $argument => $help) {            $description = preg_replace("~\R~", '', addcslashes($help['comment'], ':')) ?: $argument;            $this->stdout($argument . ':' . $description . "\n");        }        $this->stdout("\n");        foreach ($controller->getActionOptionsHelp($action) as $argument => $help) {            $description = preg_replace("~\R~", '', addcslashes($help['comment'], ':'));            $this->stdout('--' . $argument . ($description ? ':' . $description : '') . "\n");        }    }    /**     * Displays usage information for $action.     *     * @param string $action route to action     * @since 2.0.11     */    public function actionUsage($action)    {        $result = Yii::$app->createController($action);        if ($result === false || !($result[0] instanceof Controller)) {            return;        }        /** @var Controller $controller */        list($controller, $actionID) = $result;        $action = $controller->createAction($actionID);        if ($action === null) {            return;        }        $scriptName = $this->getScriptName();        if ($action->id === $controller->defaultAction) {            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));        } else {            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));        }        foreach ($controller->getActionArgsHelp($action) as $name => $arg) {            if ($arg['required']) {                $this->stdout(' <' . $name . '>', Console::FG_CYAN);            } else {                $this->stdout(' [' . $name . ']', Console::FG_CYAN);            }        }        $this->stdout("\n");    }    /**     * Returns all available command names.     * @return array all available command names     */    public function getCommands()    {        $commands = $this->getModuleCommands(Yii::$app);        sort($commands);        return array_filter(array_unique($commands), function ($command) {            $result = Yii::$app->createController($command);            if ($result === false || !$result[0] instanceof Controller) {                return false;            }            list($controller, $actionID) = $result;            $actions = $this->getActions($controller);            return $actions !== [];        });    }    /**     * 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) {            $result = Yii::$app->createController($command);            /** @var Controller $controller */            list($controller, $actionID) = $result;            $descriptions[$command] = $controller->getHelpSummary();        }        return $descriptions;    }    /**     * Returns all available actions of the specified controller.     * @param Controller $controller the controller instance     * @return array all available action IDs.     */    public function getActions($controller)    {        $actions = array_keys($controller->actions());        $class = new \ReflectionClass($controller);        foreach ($class->getMethods() as $method) {            $name = $method->getName();            if ($name !== 'actions' && $method->isPublic() && !$method->isStatic() && strncmp($name, 'action', 6) === 0) {                $actions[] = $this->camel2id(substr($name, 6));            }        }        sort($actions);        return array_unique($actions);    }    /**     * Returns available commands of a specified module.     * @param \yii\base\Module $module the module instance     * @return array the available command names     */    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)) {            $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($controllerPath, \RecursiveDirectoryIterator::KEY_AS_PATHNAME));            $iterator = new \RegexIterator($iterator, '/.*Controller\.php$/', \RecursiveRegexIterator::GET_MATCH);            foreach ($iterator as $matches) {                $file = $matches[0];                $relativePath = str_replace($controllerPath, '', $file);                $class = strtr($relativePath, [                    '/' => '\\',                    '.php' => '',                ]);                $controllerClass = $module->controllerNamespace . $class;                if ($this->validateControllerClass($controllerClass)) {                    $dir = ltrim(pathinfo($relativePath, PATHINFO_DIRNAME), '\\/');                    $command = Inflector::camel2id(substr(basename($file), 0, -14), '-', true);                    if (!empty($dir)) {                        $command = $dir . '/' . $command;                    }                    $commands[] = $prefix . $command;                }            }        }        return $commands;    }    /**     * 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');        }        return false;    }    /**     * Displays all available commands.     */    protected function getDefaultHelp()    {        $commands = $this->getCommandDescriptions();        $this->stdout($this->getDefaultHelpHeader());        if (empty($commands)) {            $this->stdout("\nNo commands are found.\n\n", Console::BOLD);            return;        }        $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);        $maxLength = 0;        foreach ($commands as $command => $description) {            $result = Yii::$app->createController($command);            /** @var $controller Controller */            list($controller, $actionID) = $result;            $actions = $this->getActions($controller);            $prefix = $controller->getUniqueId();            foreach ($actions as $action) {                $string = $prefix . '/' . $action;                if ($action === $controller->defaultAction) {                    $string .= ' (default)';                }                $maxLength = max($maxLength, strlen($string));            }        }        foreach ($commands as $command => $description) {            $result = Yii::$app->createController($command);            list($controller, $actionID) = $result;            $actions = $this->getActions($controller);            $this->stdout('- ' . $this->ansiFormat($command, Console::FG_YELLOW));            $this->stdout(str_repeat(' ', $maxLength + 4 - strlen($command)));            $this->stdout(Console::wrapText($description, $maxLength + 4 + 2), Console::BOLD);            $this->stdout("\n");            $prefix = $controller->getUniqueId();            foreach ($actions as $action) {                $string = '  ' . $prefix . '/' . $action;                $this->stdout('  ' . $this->ansiFormat($string, Console::FG_GREEN));                if ($action === $controller->defaultAction) {                    $string .= ' (default)';                    $this->stdout(' (default)', Console::FG_YELLOW);                }                $summary = $controller->getActionHelpSummary($controller->createAction($action));                if ($summary !== '') {                    $this->stdout(str_repeat(' ', $maxLength + 4 - strlen($string)));                    $this->stdout(Console::wrapText($summary, $maxLength + 4 + 2));                }                $this->stdout("\n");            }            $this->stdout("\n");        }        $scriptName = $this->getScriptName();        $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);        $this->stdout("\n  $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '            . $this->ansiFormat('<command-name>', Console::FG_CYAN) . "\n\n");    }    /**     * Displays the overall information of the command.     * @param Controller $controller the controller instance     */    protected function getCommandHelp($controller)    {        $controller->color = $this->color;        $this->stdout("\nDESCRIPTION\n", Console::BOLD);        $comment = $controller->getHelp();        if ($comment !== '') {            $this->stdout("\n$comment\n\n");        }        $actions = $this->getActions($controller);        if (!empty($actions)) {            $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);            $prefix = $controller->getUniqueId();            $maxlen = 5;            foreach ($actions as $action) {                $len = strlen($prefix . '/' . $action) + 2 + ($action === $controller->defaultAction ? 10 : 0);                $maxlen = max($maxlen, $len);            }            foreach ($actions as $action) {                $this->stdout('- ' . $this->ansiFormat($prefix . '/' . $action, Console::FG_YELLOW));                $len = strlen($prefix . '/' . $action) + 2;                if ($action === $controller->defaultAction) {                    $this->stdout(' (default)', Console::FG_GREEN);                    $len += 10;                }                $summary = $controller->getActionHelpSummary($controller->createAction($action));                if ($summary !== '') {                    $this->stdout(str_repeat(' ', $maxlen - $len + 2) . Console::wrapText($summary, $maxlen + 2));                }                $this->stdout("\n");            }            $scriptName = $this->getScriptName();            $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");        }    }    /**     * Displays the detailed information of a command action.     * @param Controller $controller the controller instance     * @param string $actionID action ID     * @throws Exception if the action does not exist     */    protected function getSubCommandHelp($controller, $actionID)    {        $action = $controller->createAction($actionID);        if ($action === null) {            $name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW);            throw new Exception("No help for unknown sub-command \"$name\".");        }        $description = $controller->getActionHelp($action);        if ($description !== '') {            $this->stdout("\nDESCRIPTION\n", Console::BOLD);            $this->stdout("\n$description\n\n");        }        $this->stdout("\nUSAGE\n\n", Console::BOLD);        $scriptName = $this->getScriptName();        if ($action->id === $controller->defaultAction) {            $this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));        } else {            $this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));        }        $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);            }        }        $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);        if (!empty($options)) {            $this->stdout(' [...options...]', Console::FG_RED);        }        $this->stdout("\n\n");        if (!empty($args)) {            foreach ($args as $name => $arg) {                $this->stdout($this->formatOptionHelp(                        '- ' . $this->ansiFormat($name, Console::FG_CYAN),                        $arg['required'],                        $arg['type'],                        $arg['default'],                        $arg['comment']) . "\n\n");            }        }        if (!empty($options)) {            $this->stdout("\nOPTIONS\n\n", Console::BOLD);            foreach ($options as $name => $option) {                $this->stdout($this->formatOptionHelp(                        $this->ansiFormat('--' . $name . $this->formatOptionAliases($controller, $name),                            Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD),                        !empty($option['required']),                        $option['type'],                        $option['default'],                        $option['comment']) . "\n\n");            }        }    }    /**     * Generates a well-formed string for an argument or option.     * @param string $name the name of the argument or option     * @param bool $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     */    protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)    {        $comment = trim($comment);        $type = trim($type);        if (strncmp($type, 'bool', 4) === 0) {            $type = 'boolean, 0 or 1';        }        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;            }            if (is_string($defaultValue)) {                $defaultValue = "'" . $defaultValue . "'";            } else {                $defaultValue = var_export($defaultValue, true);            }            $doc = "$type (defaults to $defaultValue)";        } else {            $doc = $type;        }        if ($doc === '') {            $doc = $comment;        } elseif ($comment !== '') {            $doc .= "\n" . preg_replace('/^/m', '  ', $comment);        }        $name = $required ? "$name (required)" : $name;        return $doc === '' ? $name : "$name: $doc";    }    /**     * @param Controller $controller the controller instance     * @param string $option the option name     * @return string the formatted string for the alias argument or option     * @since 2.0.8     */    protected function formatOptionAliases($controller, $option)    {        foreach ($controller->optionAliases() as $name => $value) {            if (Inflector::camel2id($value, '-', true) === $option) {                return ', -' . $name;            }        }        return '';    }    /**     * @return string the name of the cli script currently running.     */    protected function getScriptName()    {        return basename(Yii::$app->request->scriptFile);    }    /**     * Return a default help header.     * @return string default help header.     * @since 2.0.11     */    protected function getDefaultHelpHeader()    {        return "\nThis is Yii version " . \Yii::getVersion() . ".\n";    }    /**     * Converts a CamelCase action name into an ID in lowercase.     * Words in the ID are concatenated using the specified character '-'.     * For example, 'CreateUser' will be converted to 'create-user'.     * @param string $name the string to be converted     * @return string the resulting ID     */    private function camel2id($name)    {        return mb_strtolower(trim(preg_replace('/\p{Lu}/u', '-\0', $name), '-'), 'UTF-8');    }}
 |