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.
304 lines
12 KiB
PHP
304 lines
12 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) {
|
|
//region Check the argument still exists
|
|
if (!$input->hasArgument($argumentName)) {
|
|
continue;
|
|
}
|
|
//endregion
|
|
//region Get the argument value
|
|
$argumentValue = $input->getArgument($argumentName);
|
|
//endregion
|
|
//region Check the argument value is not
|
|
if ($argumentValue === null) {
|
|
// If the value is strictly Null (the default value), don't check it → skip
|
|
continue;
|
|
}
|
|
//endregion
|
|
|
|
//region Check the value is valid and replace it by the one extracted from the validator
|
|
if ($this->getDefinition()->getArgument($argumentName)->isArray() && is_array($argumentValue)) {
|
|
// This is an “array” argument → check and replace each value instead of the array itself
|
|
foreach ($argumentValue as &$argumentValueEntry) {
|
|
if (!$argumentValidator->validate($argumentValueEntry)) {
|
|
throw new InvalidValueException(sprintf('The "%s" argument has not a valid value', $argumentName));
|
|
}
|
|
$argumentValueEntry = $argumentValidator->getValue();
|
|
}
|
|
$input->setArgument($argumentName, $argumentValue);
|
|
}
|
|
else {
|
|
if (!$argumentValidator->validate($argumentValue)) {
|
|
throw new InvalidValueException(sprintf('The "%s" argument has not a valid value', $argumentName));
|
|
}
|
|
$input->setArgument($argumentName, $argumentValidator->getValue());
|
|
}
|
|
//endregion
|
|
}
|
|
}
|
|
/**
|
|
* 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) {
|
|
//region Check the option still exists
|
|
if (!$input->hasOption($optionName)) {
|
|
continue;
|
|
}
|
|
//endregion
|
|
//region Get and check the option value
|
|
$optionValue = $input->getOption($optionName);
|
|
//endregion
|
|
//region Check the option value is NOT Null or False
|
|
if ($optionValue === null || $optionValue === false) {
|
|
// If the value is strictly Null (the default value or not provided) or False (the default value), don't check it → skip
|
|
continue;
|
|
}
|
|
//endregion
|
|
|
|
//region Check the value is valid and replace it by the one extracted from the validator
|
|
if ($this->getDefinition()->getOption($optionName)->isArray() && is_array($optionValue)) {
|
|
// This is an “array” argument → check and replace each value instead of the array itself
|
|
foreach ($optionValue as &$optionValueEntry) {
|
|
if (!$optionValidator->validate($optionValueEntry)) {
|
|
throw new InvalidValueException(sprintf('The "--%s" option has not a valid value', $optionName));
|
|
}
|
|
$optionValueEntry = $optionValidator->getValue();
|
|
}
|
|
$input->setArgument($optionName, $optionValue);
|
|
}
|
|
else {
|
|
if (!$optionValidator->validate($optionValue)) {
|
|
throw new InvalidValueException(sprintf('The "--%s" option has not a valid value', $optionName));
|
|
}
|
|
$input->setOption($optionName, $optionValidator->getValue());
|
|
}
|
|
//endregion
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
} |