Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor yii\console\Controller::getActionArgsHelp() #19031

Merged
merged 21 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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