mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 14:46:19 +08:00 
			
		
		
		
	Fix #19031: Fix displaying console help for parameters with declared types
This commit is contained in:
		@ -31,6 +31,7 @@ Yii Framework 2 Change Log
 | 
				
			|||||||
- Bug #18988: Fix default value of `yii\console\controllers\MessageController::$translator` (WinterSilence)
 | 
					- 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 #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 #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)
 | 
					- Bug #19030: Add DI container usage to `yii\base\Widget::end()` (papppeter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -542,60 +542,69 @@ class Controller extends \yii\base\Controller
 | 
				
			|||||||
     * The returned value should be an array. The keys are the argument names, and the values are
 | 
					     * 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:
 | 
					     * the corresponding help information. Each value must be an array of the following structure:
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * - required: boolean, whether this argument is required.
 | 
					     * - required: bool, whether this argument is required
 | 
				
			||||||
     * - type: string, the PHP type of this argument.
 | 
					     * - type: string|null, the PHP type(s) of this argument
 | 
				
			||||||
     * - default: string, the default value of this argument
 | 
					     * - default: mixed, the default value of this argument
 | 
				
			||||||
     * - comment: string, the comment 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 default implementation will return the help information extracted from the Reflection or
 | 
				
			||||||
     * the parameters corresponding to the action method.
 | 
					     * 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
 | 
					     * @return array the help information of the action arguments
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getActionArgsHelp($action)
 | 
					    public function getActionArgsHelp($action)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $method = $this->getActionMethodReflection($action);
 | 
					        $method = $this->getActionMethodReflection($action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $tags = $this->parseDocCommentTags($method);
 | 
					        $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 = [];
 | 
					        $args = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @var \ReflectionParameter $reflection */
 | 
					        /** @var \ReflectionParameter $parameter */
 | 
				
			||||||
        foreach ($method->getParameters() as $i => $reflection) {
 | 
					        foreach ($method->getParameters() as $i => $parameter) {
 | 
				
			||||||
            if (PHP_VERSION_ID >= 80000) {
 | 
					            $type = null;
 | 
				
			||||||
                $class = $reflection->getType();
 | 
					            $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 {
 | 
					                } else {
 | 
				
			||||||
                $class = $reflection->getClass();
 | 
					                    $type = (string) $reflectionType;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // 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 type still not detected, then using type of default value
 | 
				
			||||||
 | 
					            if ($type === null && $parameter->isDefaultValueAvailable() && $parameter->getDefaultValue() !== null) {
 | 
				
			||||||
 | 
					                $type = gettype($parameter->getDefaultValue());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if ($class !== null) {
 | 
					            $args[$parameter->name] = [
 | 
				
			||||||
                continue;
 | 
					                'required' => !$parameter->isOptional(),
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            $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;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if ($reflection->isDefaultValueAvailable()) {
 | 
					 | 
				
			||||||
                $args[$name] = [
 | 
					 | 
				
			||||||
                    'required' => false,
 | 
					 | 
				
			||||||
                'type' => $type,
 | 
					                'type' => $type,
 | 
				
			||||||
                    'default' => $reflection->getDefaultValue(),
 | 
					                'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null,
 | 
				
			||||||
                'comment' => $comment,
 | 
					                'comment' => $comment,
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                $args[$name] = [
 | 
					 | 
				
			||||||
                    'required' => true,
 | 
					 | 
				
			||||||
                    'type' => $type,
 | 
					 | 
				
			||||||
                    'default' => null,
 | 
					 | 
				
			||||||
                    'comment' => $comment,
 | 
					 | 
				
			||||||
                ];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $args;
 | 
					        return $args;
 | 
				
			||||||
 | 
				
			|||||||
@ -286,18 +286,24 @@ class ControllerTest extends TestCase
 | 
				
			|||||||
        $this->assertEquals(FakeHelpController::getActionIndexLastCallParams(), ['news/posts/index']);
 | 
					        $this->assertEquals(FakeHelpController::getActionIndexLastCallParams(), ['news/posts/index']);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Tests if action help does not include (class) type hinted arguments.
 | 
					     * @see https://github.com/yiisoft/yii2/issues/19028
 | 
				
			||||||
     * @see #10372
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function testHelpSkipsTypeHintedArguments()
 | 
					    public function testGetActionArgsHelp()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $controller = new FakeController('fake', Yii::$app);
 | 
					        $controller = new FakeController('fake', Yii::$app);
 | 
				
			||||||
        $help = $controller->getActionArgsHelp($controller->createAction('with-complex-type-hint'));
 | 
					        $help = $controller->getActionArgsHelp($controller->createAction('aksi2'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->assertArrayNotHasKey('typedArgument', $help);
 | 
					        $this->assertArrayHasKey('values', $help);
 | 
				
			||||||
        $this->assertArrayHasKey('simpleArgument', $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()
 | 
					    public function testGetActionHelpSummaryOnNull()
 | 
				
			||||||
 | 
				
			|||||||
@ -57,9 +57,13 @@ class FakeController extends Controller
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public function actionAksi1($fromParam, $other = 'default')
 | 
					    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)
 | 
					    public function actionAksi2(array $values, $value)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return [$values, $value];
 | 
					        return [$values, $value];
 | 
				
			||||||
@ -89,11 +93,6 @@ class FakeController extends Controller
 | 
				
			|||||||
        return func_get_args();
 | 
					        return func_get_args();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function actionWithComplexTypeHint(self $typedArgument, $simpleArgument)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return $simpleArgument;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function actionStatus($status = 0)
 | 
					    public function actionStatus($status = 0)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $status;
 | 
					        return $status;
 | 
				
			||||||
 | 
				
			|||||||
@ -123,7 +123,7 @@ STRING
 | 
				
			|||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
        $result = Console::stripAnsiFormat($this->runControllerAction('list-action-options', ['action' => 'help/list-action-options']));
 | 
					        $result = Console::stripAnsiFormat($this->runControllerAction('list-action-options', ['action' => 'help/list-action-options']));
 | 
				
			||||||
        $this->assertEqualsWithoutLE(<<<'STRING'
 | 
					        $this->assertEqualsWithoutLE(<<<'STRING'
 | 
				
			||||||
action:route to action
 | 
					action: route to action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--interactive: whether to run the command interactively.
 | 
					--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.
 | 
					--color: whether to enable ANSI color in the output.If not set, ANSI color will only be enabled for terminals that support it.
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user