diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index 6f687d38c1084..d09c8876a7f6b 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -120,6 +120,12 @@ public function __construct(?string $name = null, ?callable $code = null) $name = array_shift($aliases); } + // we must not overwrite existing aliases, combine new ones with existing ones + $aliases = array_unique([ + ...$this->aliases, + ...$aliases, + ]); + $this->setAliases($aliases); } diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php index 3a75ea7e4a448..e56ddfbab4207 100644 --- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php @@ -199,12 +199,25 @@ public function testGetProcessedHelp() public function testGetSetAliases() { $command = new \TestCommand(); - $this->assertEquals(['name'], $command->getAliases(), '->getAliases() returns the aliases'); $ret = $command->setAliases(['name1']); $this->assertEquals($command, $ret, '->setAliases() implements a fluent interface'); $this->assertEquals(['name1'], $command->getAliases(), '->setAliases() sets the aliases'); } + public function testAliasesSetBeforeParentConstructorArePreserved() + { + $command = new class extends Command { + public function __construct() + { + // set aliases before calling parent constructor + $this->setAliases(['existingalias']); + parent::__construct('foo|newalias'); + } + }; + + $this->assertSame(['existingalias', 'newalias'], $command->getAliases(), 'Aliases set before parent::__construct() must be preserved.'); + } + #[TestWith(['name|alias1|alias2', 'name', ['alias1', 'alias2'], false])] #[TestWith(['|alias1|alias2', 'alias1', ['alias2'], true])] public function testSetAliasesAndHiddenViaName(string $name, string $expectedName, array $expectedAliases, bool $expectedHidden)