You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PhpCliProgram/src/CliProgram/Validation/TCommandWithValidation.php

250 lines
9.4 KiB
PHP

<?php
namespace jrosset\CliProgram\Validation;
use Closure;
use jrosset\CliProgram\Validation\Validators\InvalidValueException;
use jrosset\CliProgram\Validation\Validators\IValidator;
use Stringable;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/**
* Provide a value validation process ({@see IValidator}) to {@see self::addArgument()} and {@see self::addOption()}
*/
trait TCommandWithValidation {
/**
* @var array<string, IValidator> The validators of each {@see InputArgument}
*/
private array $argumentsValidator = [];
/**
* @var array<string, IValidator> The validators of each {@see InputOption}
*/
private array $optionsValidator = [];
/**
* @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);
}
/**
* Validate the command input
*
* @param InputInterface $input The input
* @param OutputInterface $output The output
*
* @return void
*/
protected function validate (InputInterface $input, OutputInterface $output): void {
$this->validateArguments($input, $output);
$this->validateOptions($input, $output);
}
/**
* Validate values of {@see InputArgument}
*
* @param InputInterface $input The input
* @param OutputInterface $output The output
*
* @return void
*
* @noinspection PhpUnusedParameterInspection
*/
protected function validateArguments (InputInterface $input, OutputInterface $output): void {
foreach ($this->argumentsValidator as $argumentName => $argumentValidator) {
if ($input->hasArgument($argumentName)) {
if (!$argumentValidator->validate($input->getArgument($argumentName))) {
throw new InvalidValueException(sprintf('The "%s" argument has not a valid value', $argumentName));
}
$input->setArgument($argumentName, $argumentValidator->getValue());
}
}
}
/**
* Validate values of {@see InputOption}
*
* @param InputInterface $input The input
* @param OutputInterface $output The output
*
* @return void
*
* @noinspection PhpUnusedParameterInspection
*/
protected function validateOptions (InputInterface $input, OutputInterface $output): void {
foreach ($this->optionsValidator as $optionName => $optionValidator) {
if ($input->hasOption($optionName)) {
if (!$optionValidator->validate($input->getOption($optionName))) {
throw new InvalidValueException(sprintf('The "--%s" option has not a valid value', $optionName));
}
$input->setOption($optionName, $optionValidator->getValue());
}
}
}
/**
* Adds an argument
*
* @param string $name The argument name
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
* @param string $description The argument description
* @param IValidator|null $validator The validator or Null if none
* @param Closure|array|null $suggestedValues The function or list of suggested values when completing ; Null if none
*
* @return $this
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function addArgument (
string $name,
int $mode = null,
string $description = '',
mixed $default = null,
?IValidator $validator = null,
Closure|array|null $suggestedValues = null
): static {
$default = static::treatStringableDefaultValue($default);
if ($validator !== null) {
$default = $validator->getValidDefault($default);
}
if ($suggestedValues !== null) {
parent::addArgument($name, $mode, $description, $default, $suggestedValues);
}
else {
parent::addArgument($name, $mode, $description, $default);
}
return $this->setArgumentValidator($name, $validator);
}
/**
* Set the validator for an {@see InputArgument}
*
* @param string $name The {@see InputArgument} name (must exist)
* @param IValidator|null $validator The validator ; Null to remove it
*
* @return $this
*
* @throws InvalidArgumentException If the {@see InputArgument} doesn't exist
*/
public function setArgumentValidator (string $name, ?IValidator $validator): static {
if (!$this->getDefinition()->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist', $name));
}
if ($validator === null) {
unset($this->argumentsValidator[$name]);
}
else {
$this->argumentsValidator[$name] = $validator;
}
return $this;
}
/**
* Get the validator for an {@see InputArgument}
*
* @param string $name The {@see InputArgument} name (must exist)
*
* @return IValidator|null The validator or Null if none
*/
public function getArgumentValidator (string $name): ?IValidator {
if (!$this->getDefinition()->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist', $name));
}
return $this->argumentsValidator[$name] ?? null;
}
/**
* Adds an option
*
* @param string $name The argument name
* @param string|string[]|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description The argument description
* @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
* @param IValidator|null $validator The validator or Null if none ; ignored if **$mode** = {@see InputOption::VALUE_NONE}
* @param Closure|array|null $suggestedValues The function or list of suggested values when completing ; Null if none
*
* @return $this
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function addOption (
string $name,
string|array|null $shortcut = null,
int $mode = null,
string $description = '',
mixed $default = null,
?IValidator $validator = null,
Closure|array|null $suggestedValues = null
): static {
$default = static::treatStringableDefaultValue($default);
if ($validator !== null) {
$default = $validator->getValidDefault($default);
}
if ($suggestedValues !== null) {
parent::addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
}
else {
parent::addOption($name, $shortcut, $mode, $description, $default);
}
return $this->setOptionValidator($name, $validator);
}
/**
* Set the validator for an {@see InputOption}
*
* @param string $name The {@see InputOption} name (must exist)
* @param IValidator|null $validator The validator ; Null to remove it
*
* @return $this
*
* @throws InvalidArgumentException If the {@see InputOption} doesn't exist
*/
public function setOptionValidator (string $name, ?IValidator $validator): static {
if (!$this->getDefinition()->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist', $name));
}
if ($validator === null) {
unset($this->optionsValidator[$name]);
}
else {
$this->optionsValidator[$name] = $validator;
}
return $this;
}
/**
* Get the validator for an {@see InputOption}
*
* @param string $name The {@see InputOption} name (must exist)
*
* @return IValidator|null The validator or Null if none
*/
public function getOptionValidator (string $name): ?IValidator {
if (!$this->getDefinition()->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist', $name));
}
return $this->optionsValidator[$name] ?? null;
}
/**
* If it is the case, transform a {@see Stringable} default value into a string
*
* @param mixed $default The default value
*
* @return mixed The default value
*/
protected static function treatStringableDefaultValue (mixed $default): mixed {
if ($default instanceof Stringable) {
return $default->__toString();
}
return $default;
}
}