diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index d8d2d33ba83..04560c76055 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -31,6 +31,7 @@ Yii Framework 2 Change Log - Bug #18988: Fix default value of `yii\console\controllers\MessageController::$translator` (WinterSilence) - Bug #18993: Load defaults by `attributes()` in `yii\db\ActiveRecord::loadDefaultValues()` (WinterSilence) - Bug #19021: Fix return type in PhpDoc `yii\db\Migration` functions `up()`, `down()`, `safeUp()` and `safeDown()` (WinterSilence, rhertogh) +- Bug #19031: Fix displaying console help for parameters with declared types (WinterSilence) - Bug #19030: Add DI container usage to `yii\base\Widget::end()` (papppeter) diff --git a/framework/console/Controller.php b/framework/console/Controller.php index a3044c76419..09495686a3d 100644 --- a/framework/console/Controller.php +++ b/framework/console/Controller.php @@ -542,60 +542,69 @@ public function getActionHelp($action) * The returned value should be an array. The keys are the argument names, and the values are * the corresponding help information. Each value must be an array of the following structure: * - * - required: boolean, whether this argument is required. - * - type: string, the PHP type of this argument. - * - default: string, the default value of this argument - * - comment: string, the comment of this argument + * - required: bool, whether this argument is required + * - type: string|null, the PHP type(s) of this argument + * - default: mixed, the default value of this argument + * - comment: string, the description of this argument * - * The default implementation will return the help information extracted from the doc-comment of - * the parameters corresponding to the action method. + * The default implementation will return the help information extracted from the Reflection or + * DocBlock of the parameters corresponding to the action method. * - * @param Action $action + * @param Action $action the action instance * @return array the help information of the action arguments */ public function getActionArgsHelp($action) { $method = $this->getActionMethodReflection($action); + $tags = $this->parseDocCommentTags($method); - $params = isset($tags['param']) ? (array) $tags['param'] : []; + $tags['param'] = isset($tags['param']) ? (array) $tags['param'] : []; + $phpDocParams = []; + foreach ($tags['param'] as $i => $tag) { + if (preg_match('/^(?\S+)(\s+\$(?\w+))?(?.*)/us', $tag, $matches) === 1) { + $key = empty($matches['name']) ? $i : $matches['name']; + $phpDocParams[$key] = ['type' => $matches['type'], 'comment' => $matches['comment']]; + } + } + unset($tags); $args = []; - /** @var \ReflectionParameter $reflection */ - foreach ($method->getParameters() as $i => $reflection) { - if (PHP_VERSION_ID >= 80000) { - $class = $reflection->getType(); - } else { - $class = $reflection->getClass(); - } - - if ($class !== null) { - continue; + /** @var \ReflectionParameter $parameter */ + foreach ($method->getParameters() as $i => $parameter) { + $type = null; + $comment = ''; + if (PHP_MAJOR_VERSION > 5 && $parameter->hasType()) { + $reflectionType = $parameter->getType(); + if (PHP_VERSION_ID >= 70100) { + $types = method_exists($reflectionType, 'getTypes') ? $reflectionType->getTypes() : [$reflectionType]; + foreach ($types as $key => $reflectionType) { + $types[$key] = $reflectionType->getName(); + } + $type = implode('|', $types); + } else { + $type = (string) $reflectionType; + } } - $name = $reflection->getName(); - $tag = isset($params[$i]) ? $params[$i] : ''; - if (preg_match('/^(\S+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) { - $type = $matches[1]; - $comment = $matches[3]; - } else { - $type = null; - $comment = $tag; + // find PhpDoc tag by property name or position + $key = isset($phpDocParams[$parameter->name]) ? $parameter->name : (isset($phpDocParams[$i]) ? $i : null); + if ($key !== null) { + $comment = $phpDocParams[$key]['comment']; + if ($type === null && !empty($phpDocParams[$key]['type'])) { + $type = $phpDocParams[$key]['type']; + } } - if ($reflection->isDefaultValueAvailable()) { - $args[$name] = [ - 'required' => false, - 'type' => $type, - 'default' => $reflection->getDefaultValue(), - 'comment' => $comment, - ]; - } else { - $args[$name] = [ - 'required' => true, - 'type' => $type, - 'default' => null, - 'comment' => $comment, - ]; + // if type still not detected, then using type of default value + if ($type === null && $parameter->isDefaultValueAvailable() && $parameter->getDefaultValue() !== null) { + $type = gettype($parameter->getDefaultValue()); } + + $args[$parameter->name] = [ + 'required' => !$parameter->isOptional(), + 'type' => $type, + 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, + 'comment' => $comment, + ]; } return $args; diff --git a/tests/framework/console/ControllerTest.php b/tests/framework/console/ControllerTest.php index d5ce60c0762..1a6dd743d7e 100644 --- a/tests/framework/console/ControllerTest.php +++ b/tests/framework/console/ControllerTest.php @@ -286,18 +286,24 @@ public function testHelpOptionWithModule() $this->assertEquals(FakeHelpController::getActionIndexLastCallParams(), ['news/posts/index']); } - /** - * Tests if action help does not include (class) type hinted arguments. - * @see #10372 + * @see https://github.com/yiisoft/yii2/issues/19028 */ - public function testHelpSkipsTypeHintedArguments() + public function testGetActionArgsHelp() { $controller = new FakeController('fake', Yii::$app); - $help = $controller->getActionArgsHelp($controller->createAction('with-complex-type-hint')); - - $this->assertArrayNotHasKey('typedArgument', $help); - $this->assertArrayHasKey('simpleArgument', $help); + $help = $controller->getActionArgsHelp($controller->createAction('aksi2')); + + $this->assertArrayHasKey('values', $help); + if (PHP_MAJOR_VERSION > 5) { + // declared type + $this->assertEquals('array', $help['values']['type']); + } else { + $this->markTestSkipped('Can not test declared type of parameter $values on PHP < 7.0'); + } + $this->assertArrayHasKey('value', $help); + // PHPDoc type + $this->assertEquals('string', $help['value']['type']); } public function testGetActionHelpSummaryOnNull() diff --git a/tests/framework/console/FakeController.php b/tests/framework/console/FakeController.php index d3a39b27748..23b38fecbc8 100644 --- a/tests/framework/console/FakeController.php +++ b/tests/framework/console/FakeController.php @@ -57,9 +57,13 @@ public function actionIndex() public function actionAksi1($fromParam, $other = 'default') { - return[$fromParam, $other]; + return [$fromParam, $other]; } + /** + * @param string $value the string value + * @return array + */ public function actionAksi2(array $values, $value) { return [$values, $value]; @@ -89,11 +93,6 @@ public function actionTrimargs($param1 = null) return func_get_args(); } - public function actionWithComplexTypeHint(self $typedArgument, $simpleArgument) - { - return $simpleArgument; - } - public function actionStatus($status = 0) { return $status; diff --git a/tests/framework/console/controllers/HelpControllerTest.php b/tests/framework/console/controllers/HelpControllerTest.php index c86d6ef40de..231accc0ed1 100644 --- a/tests/framework/console/controllers/HelpControllerTest.php +++ b/tests/framework/console/controllers/HelpControllerTest.php @@ -123,7 +123,7 @@ public function testActionListActionOptions() ]); $result = Console::stripAnsiFormat($this->runControllerAction('list-action-options', ['action' => 'help/list-action-options'])); $this->assertEqualsWithoutLE(<<<'STRING' -action:route to action +action: route to action --interactive: whether to run the command interactively. --color: whether to enable ANSI color in the output.If not set, ANSI color will only be enabled for terminals that support it.