From 08282a7c01d5b89750d050cc55a0999b041e68af Mon Sep 17 00:00:00 2001 From: Julien Rosset Date: Tue, 11 Feb 2025 10:33:35 +0100 Subject: [PATCH] Fix command with validation when using -n/--no-interaction option --- .../Validation/TCommandWithValidation.php | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/CliProgram/Validation/TCommandWithValidation.php b/src/CliProgram/Validation/TCommandWithValidation.php index e5bb0f6..7aebd9e 100644 --- a/src/CliProgram/Validation/TCommandWithValidation.php +++ b/src/CliProgram/Validation/TCommandWithValidation.php @@ -2,8 +2,10 @@ namespace jrosset\CliProgram\Validation; +use Closure; use jrosset\CliProgram\Validation\Validators\InvalidValueException; use jrosset\CliProgram\Validation\Validators\IValidator; +use ReflectionFunction; use Stringable; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; @@ -24,16 +26,59 @@ trait TCommandWithValidation { * @var array The validators of each {@see InputOption} */ private array $optionsValidator = []; + /** + * @var Closure|null The real code ({@see Command::$code} is used to perform input validation before execution) to execute when running this command. + *

If set, it overrides the code defined in the execute() method. + */ + private ?Closure $realCode = null; /** * @inheritDoc * * @throws Throwable If an argument or option is not valid */ - protected function interact (InputInterface $input, OutputInterface $output): void { - parent::interact($input, $output); - $this->validate($input, $output); + protected function initialize (InputInterface $input, OutputInterface $output): void { + parent::setCode( + function (InputInterface $input, OutputInterface $output): int { + $this->validate($input, $output); + + return $this->realCode !== null + ? ($this->realCode)($input, $output) + : $this->execute($input, $output); + } + ); + parent::initialize($input, $output); + } + /** + * @inheritDoc + * + * @noinspection PhpMissingReturnTypeInspection + */ + public function setCode (callable $code) { + if ($code instanceof Closure) { + /** @noinspection PhpUnhandledExceptionInspection */ + $codeReflection = new ReflectionFunction($code); + if ($codeReflection->getClosureThis() === null) { + set_error_handler( + static function () { + } + ); + try { + if ($rebindCode = Closure::bind($code, $this)) { + $code = $rebindCode; + } + } + finally { + restore_error_handler(); + } + } + } + + // {@see Command::$code} is used to perform input validation before execution, so set {@see self::$realCode} instead + $this->realCode = $code; + return $this; } + /** * Validate the command input *