From 3d183c440c4e22f43df7c145c02019294eba52b7 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 9 Mar 2017 19:51:40 +0100 Subject: [PATCH] [Console] Fix BC break regarding console.command event --- src/Symfony/Component/Console/Application.php | 30 ++++- .../Console/Event/ConsoleCommandEvent.php | 107 ++++++++++++++++++ .../Console/Tests/ApplicationTest.php | 51 +++++++++ 3 files changed, 183 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index d069da03bc06a..07a1deea5d553 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Event\InputDecorator; use Symfony\Component\Console\Exception\ExceptionInterface; use Symfony\Component\Console\Helper\DebugFormatterHelper; use Symfony\Component\Console\Helper\ProcessHelper; @@ -859,13 +860,32 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition } - // don't bind the input again as it would override any input argument/option set from the command event in - // addition to being useless - $command->setInputBound(true); - - $event = new ConsoleCommandEvent($command, $input, $output); + $event = new ConsoleCommandEvent($command, $eventInput = new InputDecorator($input), $output); $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + $overriddenArguments = $eventInput->getOverriddenArguments(); + $overriddenOptions = $eventInput->getOverriddenOptions(); + + if ($overriddenArguments || $overriddenOptions) { + // bind again after the console.command event to parse newly added arguments/options + try { + $input->bind($command->getDefinition()); + } catch (ExceptionInterface $e) { + } + + // mark the input as bound to prevent binding it again as it would squash any input argument/option set + // from the command event + $command->setInputBound(true); + + foreach ($overriddenArguments as $k => $v) { + $input->setArgument($k, $v); + } + + foreach ($overriddenOptions as $k => $v) { + $input->setOption($k, $v); + } + } + if ($event->commandShouldRun()) { try { $e = null; diff --git a/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php b/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php index 92adf1ef96ceb..77adaa28c621b 100644 --- a/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php +++ b/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Console\Event; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; + /** * Allows to do things before the command is executed, like skipping the command or changing the input. * @@ -60,3 +63,107 @@ public function commandShouldRun() return $this->commandShouldRun; } } + +/** + * @internal + */ +final class InputDecorator implements InputInterface +{ + private $inner; + private $overriddenArguments = array(); + private $overriddenOptions = array(); + + public function __construct(InputInterface $inner) + { + $this->inner = $inner; + } + + public function setArgument($key, $value) + { + $this->overriddenArguments[$key] = $value; + + return $this->inner->setArgument($key, $value); + } + + public function setOption($key, $value) + { + $this->overriddenOptions[$key] = $value; + + return $this->inner->setOption($key, $value); + } + + public function getOverriddenArguments() + { + return $this->overriddenArguments; + } + + public function getOverriddenOptions() + { + return $this->overriddenOptions; + } + + public function getFirstArgument() + { + return $this->inner->getFirstArgument(); + } + + public function hasParameterOption($values) + { + return $this->inner->hasParameterOption($values); + } + + public function getParameterOption($values, $default = false) + { + return $this->inner->getParameterOption($values, $default); + } + + public function bind(InputDefinition $definition) + { + return $this->inner->bind($definition); + } + + public function validate() + { + return $this->inner->validate(); + } + + public function getArguments() + { + return $this->inner->getArguments(); + } + + public function getArgument($name) + { + return $this->inner->getArgument($name); + } + + public function hasArgument($name) + { + return $this->inner->hasArgument($name); + } + + public function getOptions() + { + return $this->inner->getOptions(); + } + + public function getOption($name) + { + return $this->inner->getOption($name); + } + + public function hasOption($name) + { + return $this->inner->hasOption($name); + } + + public function isInteractive() + { + return $this->inner->isInteractive(); + } + + public function setInteractive($interactive) + { + return $this->inner->setInteractive($interactive); + } +} diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 3cec084e69734..5b2755eb9b951 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -1138,6 +1138,57 @@ public function testUpdateInputFromConsoleCommandEvent() $this->assertEquals('overriden', $tester->getInput()->getOption('extra')); } + public function testAddArgumentFromConsoleCommandEvent() + { + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) { + $event->getCommand()->addArgument('extra', InputArgument::REQUIRED); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application + ->register('foo') + ->addOption('extra', null, InputOption::VALUE_REQUIRED) + ->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }) + ; + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo', 'extra' => 'foo')); + + $this->assertEquals('foo', $tester->getInput()->getArgument('extra')); + } + + public function testAddAndSetArgumentFromConsoleCommandEvent() + { + $dispatcher = $this->getDispatcher(); + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) { + $event->getCommand()->addArgument('extra', InputArgument::REQUIRED); + $event->getInput()->setArgument('extra', 'overridden'); + }); + + $application = new Application(); + $application->setDispatcher($dispatcher); + $application->setAutoExit(false); + + $application + ->register('foo') + ->addOption('extra', null, InputOption::VALUE_REQUIRED) + ->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }) + ; + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + + $this->assertSame('overridden', $tester->getInput()->getArgument('extra')); + } + public function testTerminalDimensions() { $application = new Application();