8000 feature #33412 [Console] Do not leak hidden console commands (m-vo) · symfony/symfony@e627989 · GitHub
[go: up one dir, main page]

Skip to content

Commit e627989

Browse files
committed
feature #33412 [Console] Do not leak hidden console commands (m-vo)
This PR was merged into the 4.4 branch. Discussion ---------- [Console] Do not leak hidden console commands | Q | A | ------------- | --- | Branch? | 4.4 (updated) | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #33398 | License | MIT This PR attempts to fix hidden console commands to be leaked when interacting with the console. These are the changes: * Hidden commands won't be shown anymore in the list of commands in a namespace as well as the list of suggestions ("Did you mean...") for invalid or ambiguous commands. * Hidden commands therefore now need to be always entered with their full name. * If an abbreviated command is entered that was previously ambiguous with (only) hidden commands, it's now executed directly (not ambiguous anymore). Side note: When implementing the tests & changes I realized that `Application->get()` isn't side effect free (when redirecting to the help command) and behaves differently when called multiple times. It therefore must not be used from inside `find()`. Maybe we should change this? Here are the relevant bits: https://github.com/symfony/symfony/blob/f71f74b36a80227d3e68f1b65b1f1d9b42fa9952/src/Symfony/Component/Console/Application.php#L495-L502 Commits ------- f340633 [Console] Deprecate abbreviating hidden command names using Application->find()
2 parents 399e0fb + f340633 commit e627989

File tree

6 files changed

+97
-4
lines changed

6 files changed

+97
-4
lines changed

UPGRADE-4.4.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Cache
66

77
* Added argument `$prefix` to `AdapterInterface::clear()`
88

9+
Console
10+
-------
11+
12+
* Deprecated finding hidden commands using an abbreviation, use the full name instead
13+
914
Debug
1015
-----
1116

UPGRADE-5.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Config
3131
Console
3232
-------
3333

34+
* Removed support for finding hidden commands using an abbreviation, use the full name instead
3435
* Removed the `setCrossingChar()` method in favor of the `setDefaultCrossingChar()` method in `TableStyle`.
3536
* Removed the `setHorizontalBorderChar()` method in favor of the `setDefaultCrossingChars()` method in `TableStyle`.
3637
* Removed the `getHorizontalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`.

src/Symfony/Component/Console/Application.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -692,25 +692,36 @@ public function find($name)
692692
foreach ($abbrevs as $abbrev) {
693693
$maxLen = max(Helper::strlen($abbrev), $maxLen);
694694
}
695-
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
695+
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
696696
if (!$commandList[$cmd] instanceof Command) {
697697
$commandList[$cmd] = $this->commandLoader->get($cmd);
698698
}
699699

700700
if ($commandList[$cmd]->isHidden()) {
701+
unset($commands[array_search($cmd, $commands)]);
702+
701703
return false;
702704
}
703705

704706
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
705707

706708
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
707709
}, array_values($commands));
708-
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
709710

710-
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
711+
if (\count($commands) > 1) {
712+
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
713+
714+
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
715+
}
711716
}
712717

713-
return $this->get(reset($commands));
718+
$command = $this->get(reset($commands));
719+
720+
if ($command->isHidden()) {
721+
@trigger_error(sprintf('Command "%s" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.', $command->getName()), E_USER_DEPRECATED);
722+
}
723+
724+
return $command;
714725
}
715726

716727
/**

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* deprecated finding hidden commands using an abbreviation, use the full name instead
78
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
89
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
910
* `Application` implements `ResetInterface`

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public static function setUpBeforeClass(): void
7676
require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering.php';
7777
require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering2.php';
7878
require_once self::$fixturesPath.'/FooHiddenCommand.php';
79+
require_once self::$fixturesPath.'/BarHiddenCommand.php';
7980
}
8081

8182
protected function normalizeLineBreaks($text)
@@ -441,6 +442,16 @@ public function provideAmbiguousAbbreviations()
441442
];
442443
}
443444

445+
public function testFindWithAmbiguousAbbreviationsFindsCommandIfAlternativesAreHidden()
446+
{
447+
$application = new Application();
448+
449+
$application->add(new \FooCommand());
450+
$application->add(new \FooHiddenCommand());
451+
452+
$this->assertInstanceOf('FooCommand', $application->find('foo:'));
453+
}
454+
5957
444455
public function testFindCommandEqualNamespace()
445456
{
446457
$application = new Application();
@@ -708,6 +719,49 @@ public function testFindWithDoubleColonInNameThrowsException()
708719
$application->find('foo::bar');
709720
}
710721

722+
public function testFindHiddenWithExactName()
723+
{
724+
$application = new Application();
725+
$application->add(new \FooHiddenCommand());
726+
727+
$this->assertInstanceOf('FooHiddenCommand', $application->find('foo:hidden'));
728+
$this->assertInstanceOf('FooHiddenCommand', $application->find('afoohidden'));
729+ }
730+
731+
/**
732+
* @group legacy
733+
* @expectedDeprecation Command "%s:hidden" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.
734+
* @dataProvider provideAbbreviationsForHiddenCommands
735+
*/
736+
public function testFindHiddenWithAbbreviatedName($name)
737+
{
738+
$application = new Application();
739+
740+
$application->add(new \FooHiddenCommand());
741+
$application->add(new \BarHiddenCommand());
742+
743+
$application->find($name);
744+
}
745+
746+
public function provideAbbreviationsForHiddenCommands()
747+
{
748+
return [
749+
['foo:hidde'],
750+
['afoohidd'],
751+
['bar:hidde'],
752+
];
753+
}
754+
755+
public function testFindAmbiguousCommandsIfAllAlternativesAreHidden()
756+
{
757+
$application = new Application();
758+
759+
$application->add(new \FooCommand());
760+
$application->add(new \FooHiddenCommand());
761+
762+
$this->assertInstanceOf('FooCommand', $application->find('foo:'));
763+
}
764+
711765
public function testSetCatchExceptions()
712766
{
713767
$application = new Application();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use Symfony\Component\Console\Command\Command;
4+
use Symfony\Component\Console\Input\InputInterface;
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
class BarHiddenCommand extends Command
8+
{
9+
protected function configure()
10+
{
11+
$this
12+
->setName('bar:hidden')
13+
->setAliases(['abarhidden'])
14+
->setHidden(true)
15+
;
16+
}
17+
18+
protected function execute(InputInterface $input, OutputInterface $output)
19+
{
20+
}
21+
}

0 commit comments

Comments
 (0)
0