From 40663b49fbddbe72db81ec8a84233e8c31c5c6d1 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Fri, 3 Jun 2022 14:46:29 +0200 Subject: [PATCH 1/5] [Console] Escape % in command name & description from getDefault*() --- DependencyInjection/AddConsoleCommandPass.php | 4 +-- .../AddConsoleCommandPassTest.php | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index 743e306d0..2c9ebe668 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -67,7 +67,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $aliases = $class::getDefaultName(); + $aliases = str_replace('%', '%%', $class::getDefaultName()); } $aliases = explode('|', $aliases ?? ''); @@ -124,7 +124,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $description = $class::getDefaultDescription(); + $description = str_replace('%', '%%', $class::getDefaultDescription()); } if ($description) { diff --git a/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/Tests/DependencyInjection/AddConsoleCommandPassTest.php index aa92c76f1..eec4eff43 100644 --- a/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -153,6 +153,33 @@ public function testProcessFallsBackToDefaultDescription() $this->assertSame(1 + $initCounter, DescribedCommand::$initCounter); } + public function testEscapesDefaultFromPhp() + { + $container = new ContainerBuilder(); + $container + ->register('to-escape', EscapedDefaultsFromPhpCommand::class) + ->addTag('console.command') + ; + + $pass = new AddConsoleCommandPass(); + $pass->process($container); + + $commandLoader = $container->getDefinition('console.command_loader'); + $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0)); + + $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass()); + $this->assertSame(['%%cmd%%' => 'to-escape', '%%cmdalias%%' => 'to-escape'], $commandLoader->getArgument(1)); + $this->assertEquals([['to-escape' => new ServiceClosureArgument(new Reference('.to-escape.lazy'))]], $commandLocator->getArguments()); + $this->assertSame([], $container->getParameter('console.command.ids')); + + $command = $container->get('console.command_loader')->get('%%cmd%%'); + + $this->assertInstanceOf(LazyCommand::class, $command); + $this->assertSame('%cmd%', $command->getName()); + $this->assertSame(['%cmdalias%'], $command->getAliases()); + $this->assertSame('Creates a 80% discount', $command->getDescription()); + } + public function testProcessThrowAnExceptionIfTheServiceIsAbstract() { $this->expectException(\InvalidArgumentException::class); @@ -286,6 +313,12 @@ class NamedCommand extends Command protected static $defaultName = 'default'; } +class EscapedDefaultsFromPhpCommand extends Command +{ + protected static $defaultName = '%cmd%|%cmdalias%'; + protected static $defaultDescription = 'Creates a 80% discount'; +} + class DescribedCommand extends Command { public static $initCounter = 0; From b4a1d2d45124b7ca87ec1e3bae40455c6be0cad8 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Fri, 3 Jun 2022 14:46:29 +0200 Subject: [PATCH 2/5] [Console] Escape % in command name & description from getDefault*() --- DependencyInjection/AddConsoleCommandPass.php | 2 +- .../AddConsoleCommandPassTest.php | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index 666c8fa59..aff892cc2 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -55,7 +55,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $commandName = $class::getDefaultName(); + $commandName = $class::getDefaultName() !== null ? str_replace('%', '%%', $class::getDefaultName()) : null; } if (null === $commandName) { diff --git a/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/Tests/DependencyInjection/AddConsoleCommandPassTest.php index 5e59f8fab..9c86fa98f 100644 --- a/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -118,6 +118,30 @@ public function visibilityProvider() ]; } + public function testEscapesDefaultFromPhp() + { + $container = new ContainerBuilder(); + $container + ->register('to-escape', EscapedDefaultsFromPhpCommand::class) + ->addTag('console.command') + ; + + $pass = new AddConsoleCommandPass(); + $pass->process($container); + + $commandLoader = $container->getDefinition('console.command_loader'); + $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0)); + + $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass()); + $this->assertSame(['%%cmd%%' => 'to-escape'], $commandLoader->getArgument(1)); + $this->assertEquals([['to-escape' => new ServiceClosureArgument(new TypedReference('to-escape', EscapedDefaultsFromPhpCommand::class))]], $commandLocator->getArguments()); + $this->assertSame([], $container->getParameter('console.command.ids')); + + $command = $container->get('console.command_loader')->get('%%cmd%%'); + + $this->assertSame('%cmd%', $command->getName()); + } + public function testProcessThrowAnExceptionIfTheServiceIsAbstract() { $this->expectException(\InvalidArgumentException::class); @@ -250,3 +274,8 @@ class NamedCommand extends Command { protected static $defaultName = 'default'; } + +class EscapedDefaultsFromPhpCommand extends Command +{ + protected static $defaultName = '%cmd%'; +} From 948192497e2bbbb697571a7590ee63d041b21e72 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 6 Jun 2022 14:28:06 +0200 Subject: [PATCH 3/5] [Console] Prevent PHP 8.1 str_replace deprec on null Prevents: > Deprecated: str_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated on `getDefaultName()` returning `null` --- DependencyInjection/AddConsoleCommandPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index 2c9ebe668..c55c5db4e 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -67,7 +67,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $aliases = str_replace('%', '%%', $class::getDefaultName()); + $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); } $aliases = explode('|', $aliases ?? ''); From 56f40c96d6619cd87717a066f7ce65054bfd38b1 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 7 Jun 2022 04:10:23 +0200 Subject: [PATCH 4/5] [Console] Fix deprecation when description is null str_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated --- DependencyInjection/AddConsoleCommandPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index c55c5db4e..1fbb212e7 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -124,7 +124,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $description = str_replace('%', '%%', $class::getDefaultDescription()); + $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } if ($description) { From 77972627c95a1c36eefbc1776dbe98dc4ea1d852 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 23 Jun 2022 12:21:08 +0200 Subject: [PATCH 5/5] Fix global state pollution between tests run with ApplicationTester --- Tester/ApplicationTester.php | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/Tester/ApplicationTester.php b/Tester/ApplicationTester.php index 4f99da18d..ce4e5c18d 100644 --- a/Tester/ApplicationTester.php +++ b/Tester/ApplicationTester.php @@ -54,17 +54,37 @@ public function __construct(Application $application) */ public function run(array $input, $options = []) { - $this->input = new ArrayInput($input); - if (isset($options['interactive'])) { - $this->input->setInteractive($options['interactive']); - } + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); - if ($this->inputs) { - $this->input->setStream(self::createStream($this->inputs)); - } + try { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } - $this->initOutput($options); + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } - return $this->statusCode = $this->application->run($this->input, $this->output); + $this->initOutput($options); + + return $this->statusCode = $this->application->run($this->input, $this->output); + } finally { + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one test's verbosity to spread to the following tests + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } + } } }