Skip to content

Commit

Permalink
Fix #19031: Fix displaying console help for parameters with declared …
Browse files Browse the repository at this point in the history
…types
  • Loading branch information
WinterSilence committed Dec 1, 2021
1 parent 0d899fa commit 766cf70
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 55 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
89 changes: 49 additions & 40 deletions framework/console/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('/^(?<type>\S+)(\s+\$(?<name>\w+))?(?<comment>.*)/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;
Expand Down
22 changes: 14 additions & 8 deletions tests/framework/console/ControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
11 changes: 5 additions & 6 deletions tests/framework/console/FakeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tests/framework/console/controllers/HelpControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 766cf70

Please sign in to comment.