Add "validation" module

2.x
Julien Rosset 2 years ago
parent a8d04db3f1
commit 6b77ed1c42

@ -15,11 +15,11 @@
"minimum-stability": "stable",
"require": {
"php": "^7.4 || ^8.0",
"symfony/console": "^5.4 || ^6.0",
"php": "^8.1",
"symfony/console": "^6.1",
"jrosset/betterphptoken": "^1.0",
"jrosset/collections": "^2.3 || ^3.0",
"jrosset/extendedmonolog": "^1.0 || ^2.0"
"jrosset/collections": "^3.0",
"jrosset/extendedmonolog": "^2.0"
},
"autoload": {
"psr-4": {

@ -0,0 +1,13 @@
<?php
namespace jrosset\CliProgram\Validation;
use jrosset\CliProgram\BaseCommand;
use jrosset\CliProgram\Validation\Validators\IValidator;
/**
* A command with a value validation process ({@see IValidator}) to {@see self::addArgument()} and {@see self::addOption()}
*/
class CommandWithValidation extends BaseCommand {
use TCommandWithValidation;
}

@ -0,0 +1,250 @@
<?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->argumentsValidator[$name]);
}
else {
$this->argumentsValidator[$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->argumentsValidator[$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;
}
}

@ -0,0 +1,38 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use DateTimeInterface;
/**
* An argument/option value validator expecting a date and/or time
*
* @template-implements BasedValidator<RegexValidator>
*/
abstract class AbstractDateTimeValidator extends BasedValidator {
/**
* Create a validator
*/
public function __construct () {
parent::__construct(new RegexValidator($this->getPattern()));
}
/**
* @inheritDoc
*/
public function validate (mixed $value): bool {
$this->getInternalValidator()->setPattern($this->getPattern());
return parent::validate($value);
}
/**
* The date and/or time regex pattern
*
* @return string The date and/or time regex pattern
*/
protected abstract function getPattern (): string;
/**
* @inheritDoc
*/
public abstract function getValue (): DateTimeInterface;
}

@ -0,0 +1,39 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
/**
* An argument/option value validator based on another, internal, validator
*
* @template TValidator of IValidator
*/
abstract class BasedValidator implements IValidator {
/**
* @var TValidator The internal validator
*/
private $internalValidator;
/**
* Create a validator
*
* @param TValidator $internalValidator The internal validator
*/
public function __construct ($internalValidator) {
$this->internalValidator = $internalValidator;
}
/**
* The internal validator
*
* @return TValidator The internal validator
*/
protected function getInternalValidator () {
return $this->internalValidator;
}
/**
* @inheritDoc
*/
public function validate (mixed $value): bool {
return $this->internalValidator->validate($value);
}
}

@ -0,0 +1,85 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use DateTimeImmutable;
use DateTimeInterface;
/**
* An argument/option value validator expecting a date and time
*
* @template-implements BasedValidator<RegexValidator>
*/
class DateTimeValidator extends AbstractDateTimeValidator {
/**
* @var bool Is the time part mandatory ?
*/
private bool $timeMandatory;
/**
* Create a validator
*
* @param bool $mandatoryDate Is the time part mandatory ?
*/
public function __construct (bool $mandatoryDate = false) {
$this->setTimeMandatory($mandatoryDate);
parent::__construct();
}
/**
* @inheritDoc
*/
public function getValidDefault (mixed $default): string|bool|int|float|array {
if ($default instanceof DateTimeInterface) {
return $default->format('Y-m-d H:i:s');
}
return $default;
}
/**
* @inheritDoc
*/
protected function getPattern (): string {
/** @noinspection RegExpUnnecessaryNonCapturingGroup */
return /** @lang PhpRegExp */ '/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})(?: (?<hour>\d{2}):(?<minute>\d{2})(?::(?<second>\d{2}))?)' . ($this->isTimeMandatory() ? '' : '?')
. '$/';
}
/**
* @inheritDoc
*/
public function getValue (): DateTimeInterface {
$value = new DateTimeImmutable();
$value->setDate(
(int)$this->getInternalValidator()->getMatch('year'),
(int)$this->getInternalValidator()->getMatch('month'),
(int)$this->getInternalValidator()->getMatch('day'),
);
$value->setTime(
(int)($this->getInternalValidator()->getMatch('hour') ?? 0),
(int)($this->getInternalValidator()->getMatch('minute') ?? 0),
(int)($this->getInternalValidator()->getMatch('second') ?? 0),
);
return $value;
}
/**
* Is the time part mandatory ?
*
* @return bool Is the time part mandatory ?
*/
public function isTimeMandatory (): bool {
return $this->timeMandatory;
}
/**
* Is the time part mandatory ?
*
* @param bool $timeMandatory Is the time part mandatory ?
*
* @return $this
*/
public function setTimeMandatory (bool $timeMandatory): self {
$this->timeMandatory = $timeMandatory;
return $this;
}
}

@ -0,0 +1,82 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use DateTimeImmutable;
use DateTimeInterface;
/**
* An argument/option value validator expecting a date (without time)
*
* @template-implements BasedValidator<RegexValidator>
*/
class DateValidator extends AbstractDateTimeValidator {
/**
* @var DateTimeInterface The time part
*/
private DateTimeInterface $timePart;
/**
* Create a validator
*
* @param DateTimeInterface|null $datePart The time part
*/
public function __construct (?DateTimeInterface $datePart = null) {
$this->setTimePart($datePart ?? (new DateTimeImmutable())->setTimestamp(0));
parent::__construct();
}
/**
* @inheritDoc
*/
public function getValidDefault (mixed $default): string|bool|int|float|array {
if ($default instanceof DateTimeInterface) {
return $default->format('Y-m-d');
}
return $default;
}
/**
* @inheritDoc
*/
protected function getPattern (): string {
return /** @lang PhpRegExp */ '/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/';
}
/**
* @inheritDoc
*/
public function getValue (): DateTimeInterface {
$value = new DateTimeImmutable();
$value->setDate(
(int)$this->getInternalValidator()->getMatch('year'),
(int)$this->getInternalValidator()->getMatch('month'),
(int)$this->getInternalValidator()->getMatch('day'),
);
$value->setTime(
(int)$this->getTimePart()->format('%H'),
(int)$this->getTimePart()->format('%i'),
(int)$this->getTimePart()->format('%s'),
);
return $value;
}
/**
* The time part
*
* @return DateTimeInterface The time part
*/
public function getTimePart (): DateTimeInterface {
return $this->timePart;
}
/**
* Set the time part
*
* @param DateTimeInterface $timePart The time part
*
* @return $this
*/
public function setTimePart (DateTimeInterface $timePart): self {
$this->timePart = $timePart;
return $this;
}
}

@ -0,0 +1,135 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use InvalidArgumentException;
/**
* An argument/option value validator expecting a decimal
*
* @template-implements BasedValidator<RegexValidator>
* @template-implements TInternalValueValidator<float>
*/
class DecimalValidator extends BasedValidator {
use TIdenticalValidDefaultValidator;
use TInternalValueValidator;
/**
* @var string The locale thousands separator
*/
private string $decimalSeparator;
/**
* @var string The locale thousands separator
*/
private string $thousandsSeparator;
/**
* @var float|null The minimal value allowed, Null if none
*/
private ?float $minAllowedValue = null;
/**
* @var float|null The maximal value allowed, Null if none
*/
private ?float $maxAllowedValue = null;
/**
* Create a validator
*
* @param float|null $minAllowedValue The minimal value allowed, Null if none
* @param float|null $maxAllowedValue The maximal value allowed, Null if none
*/
public function __construct (?float $minAllowedValue, ?float $maxAllowedValue) {
$locale = localeconv();
$this->decimalSeparator = preg_quote($locale['decimal_point'], '/');
$this->thousandsSeparator = preg_quote($locale['thousands_sep'], '/');
$this->setMinAllowedValue($minAllowedValue);
$this->setMaxAllowedValue($maxAllowedValue);
$this->value = 0;
parent::__construct(
new RegexValidator(
/** @lang PhpRegExp */ '/^(?<int>[+-]?(?:\d{1,3}' . $this->thousandsSeparator . '?(?:\d{3}' . $this->thousandsSeparator . '?)*\d{3}|\d{1,3}))(?:' . $this->decimalSeparator
. '(?<dec>\d+))?$/'
)
);
}
/**
* @inheritDoc
*/
public function validate (mixed $value): bool {
if (!parent::validate($value)) {
return false;
}
$this->setValue(
(float)(
preg_replace(
[
'/^+/',
'/' . $this->thousandsSeparator . '/',
],
[
'',
'',
],
$this->getInternalValidator()->getMatch('int')
)
. '.' . $this->getInternalValidator()->getMatch('dex')
)
);
if ($this->getMinAllowedValue() !== null && $value < $this->getMinAllowedValue()) {
return false;
}
if ($this->getMaxAllowedValue() !== null && $value > $this->getMaxAllowedValue()) {
return false;
}
return true;
}
/**
* The minimal value allowed, Null if none
*
* @return float|null The minimal value allowed, Null if none
*/
public function getMinAllowedValue (): ?float {
return $this->minAllowedValue;
}
/**
* Set the minimal value allowed
*
* @param float|null $minAllowedValue The minimal value allowed, Null if none
*
* @return $this
*/
public function setMinAllowedValue (?float $minAllowedValue): self {
if ($minAllowedValue !== null && $this->getMaxAllowedValue() !== null && $minAllowedValue > $this->getMaxAllowedValue()) {
throw new InvalidArgumentException('The minimal allowed value must be null or lesser than the maximal allowed value');
}
$this->minAllowedValue = $minAllowedValue;
return $this;
}
/**
* The maximal value allowed, Null if none
*
* @return float|null The maximal value allowed, Null if none
*/
public function getMaxAllowedValue (): ?float {
return $this->maxAllowedValue;
}
/**
* Set the maximal value allowed
*
* @param float|null $maxAllowedValue The maximal value allowed, Null if none
*
* @return $this
*/
public function setMaxAllowedValue (?float $maxAllowedValue): self {
if ($maxAllowedValue !== null && $this->getMinAllowedValue() !== null && $maxAllowedValue < $this->getMinAllowedValue()) {
throw new InvalidArgumentException('The maximal allowed value must be null or greater than the minimal allowed value');
}
$this->maxAllowedValue = $maxAllowedValue;
return $this;
}
}

@ -0,0 +1,34 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
/**
* An argument/option value validator
*
* @template TValue
*/
interface IValidator {
/**
* Get a valid default value from the initial/given default value
*
* @param mixed $default The initial/given default value
*
* @return string|bool|int|float|array The valid default value
*/
public function getValidDefault (mixed $default): string|bool|int|float|array;
/**
* Validate a value
*
* @param mixed $value The value to validate
*
* @return bool True if the value is valid, else False
*/
public function validate (mixed $value): bool;
/**
* Get the value, after it's validation
*
* @return TValue|null The value
*/
public function getValue ();
}

@ -0,0 +1,126 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use InvalidArgumentException;
/**
* An argument/option value validator expecting an integer
*
* @template-implements BasedValidator<RegexValidator>
* @template-implements TInternalValueValidator<int>
*/
class IntegerValidator extends BasedValidator {
use TIdenticalValidDefaultValidator;
use TInternalValueValidator;
/**
* @var string The locale thousands separator
*/
private string $thousandsSeparator;
/**
* @var int|null The minimal value allowed, Null if none
*/
private ?int $minAllowedValue = null;
/**
* @var int|null The maximal value allowed, Null if none
*/
private ?int $maxAllowedValue = null;
/**
* Create a validator
*
* @param int|null $minAllowedValue The minimal value allowed, Null if none
* @param int|null $maxAllowedValue The maximal value allowed, Null if none
*/
public function __construct (?int $minAllowedValue, ?int $maxAllowedValue) {
$locale = localeconv();
$this->thousandsSeparator = preg_quote($locale['thousands_sep'], '/');
$this->setMinAllowedValue($minAllowedValue);
$this->setMaxAllowedValue($maxAllowedValue);
$this->value = 0;
parent::__construct(
new RegexValidator(
/** @lang PhpRegExp */ '/^[+-]?(?:\d{1,3}' . $this->thousandsSeparator . '?(?:\d{3}' . $this->thousandsSeparator . '?)*\d{3}|\d{1,3})$/'
)
);
}
/**
* @inheritDoc
*/
public function validate (mixed $value): bool {
if (!parent::validate($value)) {
return false;
}
$this->setValue(
(int)preg_replace(
[
'/^\+/',
'/' . $this->thousandsSeparator . '/',
],
[
'',
'',
],
$value
)
);
if ($this->getMinAllowedValue() !== null && $value < $this->getMinAllowedValue()) {
return false;
}
if ($this->getMaxAllowedValue() !== null && $value > $this->getMaxAllowedValue()) {
return false;
}
return true;
}
/**
* The minimal value allowed, Null if none
*
* @return int|null The minimal value allowed, Null if none
*/
public function getMinAllowedValue (): ?int {
return $this->minAllowedValue;
}
/**
* Set the minimal value allowed
*
* @param int|null $minAllowedValue The minimal value allowed, Null if none
*
* @return $this
*/
public function setMinAllowedValue (?int $minAllowedValue): self {
if ($minAllowedValue !== null && $this->getMaxAllowedValue() !== null && $minAllowedValue > $this->getMaxAllowedValue()) {
throw new InvalidArgumentException('The minimal allowed value must be null or lesser than the maximal allowed value');
}
$this->minAllowedValue = $minAllowedValue;
return $this;
}
/**
* The maximal value allowed, Null if none
*
* @return int|null The maximal value allowed, Null if none
*/
public function getMaxAllowedValue (): ?int {
return $this->maxAllowedValue;
}
/**
* Set the maximal value allowed
*
* @param int|null $maxAllowedValue The maximal value allowed, Null if none
*
* @return $this
*/
public function setMaxAllowedValue (?int $maxAllowedValue): self {
if ($maxAllowedValue !== null && $this->getMinAllowedValue() !== null && $maxAllowedValue < $this->getMinAllowedValue()) {
throw new InvalidArgumentException('The maximal allowed value must be null or greater than the minimal allowed value');
}
$this->maxAllowedValue = $maxAllowedValue;
return $this;
}
}

@ -0,0 +1,11 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use RuntimeException;
/**
* An invalid value for an argument/option
*/
class InvalidValueException extends RuntimeException {
}

@ -0,0 +1,63 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use jrosset\Collections\Collection;
use jrosset\Collections\ICollection;
/**
* An argument/option value validator based on a list of value
*
* @template TValue
* @template-implements IValidator<TValue>
* @template-implements TInternalValueValidator<TValue>
*/
class ListValidator implements IValidator {
use TIdenticalValidDefaultValidator;
use TInternalValueValidator;
/**
* @var ICollection The list of allowed values
*/
private ICollection $allowedValues;
/**
* Create a validator
*
* @param ICollection|null $allowedValues The list of allowed values
*/
public function __construct (?ICollection $allowedValues = null) {
$this->setAllowedValues($allowedValues ?? new Collection());
}
/**
* @inheritDoc
*/
public function validate (mixed $value): bool {
if ($this->getAllowedValues()->contains($value)) {
$this->setValue($value);
return true;
}
return false;
}
/**
* The list of allowed values
*
* @return ICollection The list of allowed values
*/
public function getAllowedValues (): ICollection {
return $this->allowedValues;
}
/**
* Set the list of allowed values
*
* @param ICollection $allowedValues The list of allowed values
*
* @return $this
*/
public function setAllowedValues (ICollection $allowedValues): self {
$this->allowedValues = $allowedValues;
return $this;
}
}

@ -0,0 +1,94 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
/**
* An argument/option value validator based on a regex
*/
class RegexValidator implements IValidator {
use TIdenticalValidDefaultValidator;
public const GROUP_VALUE = 'value';
/**
* @var string The regex pattern
*/
private string $pattern;
/**
* @var string[] The capturing groups
*/
private array $matches;
/**
* Create a validator
*
* @param string $pattern The regex pattern
*/
public function __construct (string $pattern) {
$this->pattern = $pattern;
$this->matches = [];
}
/**
* @inheritDoc
*/
public function validate (mixed $value): bool {
return preg_match($this->pattern, $value, $this->matches) === 1;
}
/**
* The regex pattern
*
* @return string The regex pattern
*/
public function getPattern (): string {
return $this->pattern;
}
/**
* Set the regex pattern
*
* @param string $pattern The regex pattern
*
* @return $this
*/
public function setPattern (string $pattern): self {
$this->pattern = $pattern;
return $this;
}
/**
* The capturing groups
*
* @return string[] The capturing groups
*/
public function getMatches (): array {
return $this->matches;
}
/**
* A capturing group
*
* @param int|string $group The group name/index
*
* @return string|null The capturing group or Null if not set
*/
public function getMatch (int|string $group): ?string {
return $this->getMatches()[$group] ?? null;
}
/**
* Has a capturing group ?
*
* @param int|string $group The group name/index
*
* @return bool True if the capturing group exists and is not null
*/
public function hasMatch (int|string $group): bool {
return $this->getMatches()[$group] !== null;
}
/**
* @inheritDoc
*/
public function getValue (): ?string {
return $this->getMatch(static::GROUP_VALUE) ?? $this->getMatch(0);
}
}

@ -0,0 +1,17 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
/**
* Implementation to get a valid default value identical to the initial/given default value
*
* @see IValidator
*/
trait TIdenticalValidDefaultValidator {
/**
* @inheritDoc
*/
public function getValidDefault (mixed $default): string|bool|int|float|array {
return $default;
}
}

@ -0,0 +1,39 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace jrosset\CliProgram\Validation\Validators;
/**
* Implementation for an internal "value"
*
* Call {@see self::setValue()} to set the value
*
* @template TValue
* @template-implements IValidator<TValue>
*/
trait TInternalValueValidator {
/**
* @var TValue|null The current value, after
*/
private $value = null;
/**
* Get the value, after it's validation
*
* @return TValue|null The value
*/
public function getValue () {
return $this->value;
}
/**
* Set the value
*
* @param TValue $value The value (after it's validation)
*
* @return $this
*/
protected function setValue ($value): static {
$this->value = $value;
return $this;
}
}

@ -0,0 +1,82 @@
<?php
namespace jrosset\CliProgram\Validation\Validators;
use DateTimeImmutable;
use DateTimeInterface;
/**
* An argument/option value validator expecting a date (without time)
*
* @template-implements BasedValidator<RegexValidator>
*/
class TimeValidator extends AbstractDateTimeValidator {
/**
* @var DateTimeInterface The date part
*/
private DateTimeInterface $datePart;
/**
* Create a validator
*
* @param DateTimeInterface|null $datePart The date part
*/
public function __construct (?DateTimeInterface $datePart = null) {
$this->setDatePart($datePart ?? (new DateTimeImmutable())->setTimestamp(0));
parent::__construct();
}
/**
* @inheritDoc
*/
public function getValidDefault (mixed $default): string|bool|int|float|array {
if ($default instanceof DateTimeInterface) {
return $default->format('H:i:s');
}
return $default;
}
/**
* @inheritDoc
*/
protected function getPattern (): string {
return /** @lang PhpRegExp */ '/^(?<hour>\d{2}):(?<minute>\d{2})(?::(?<second>\d{2}))?$/';
}
/**
* @inheritDoc
*/
public function getValue (): DateTimeInterface {
$value = new DateTimeImmutable();
$value->setDate(
(int)$this->getDatePart()->format('%Y'),
(int)$this->getDatePart()->format('%m'),
(int)$this->getDatePart()->format('%d'),
);
$value->setTime(
(int)($this->getInternalValidator()->getMatch('hour') ?? 0),
(int)($this->getInternalValidator()->getMatch('minute') ?? 0),
(int)($this->getInternalValidator()->getMatch('second') ?? 0),
);
return $value;
}
/**
* The date part
*
* @return DateTimeInterface The date part
*/
public function getDatePart (): DateTimeInterface {
return $this->datePart;
}
/**
* Set the date part
*
* @param DateTimeInterface $datePart The date part
*
* @return $this
*/
public function setDatePart (DateTimeInterface $datePart): self {
$this->datePart = $datePart;
return $this;
}
}

@ -2,13 +2,15 @@
namespace jrosset\Tests\Commands;
use jrosset\CliProgram\BaseCommand;
use jrosset\CliProgram\Monolog\ConsoleOutputWithMonolog;
use jrosset\CliProgram\Validation\CommandWithValidation;
use jrosset\CliProgram\Validation\Validators\IntegerValidator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Hello extends BaseCommand {
class Hello extends CommandWithValidation {
/**
* @inheritdoc
*/
@ -21,12 +23,31 @@ class Hello extends BaseCommand {
parent::__construct('hello', 'bonjour');
}
/**
* @inheritDoc
*/
protected function configure () {
parent::configure();
$this->addArgument(
'repeat',
InputArgument::OPTIONAL,
'The number of repeat',
1,
new IntegerValidator(1, null)
);
}
/**
* @inheritDoc
*/
protected function execute (InputInterface $input, OutputInterface $output): int {
$output->writeln('Command : ' . __CLASS__, OutputInterface::VERBOSITY_DEBUG);
$repeat = $input->getArgument('repeat');
var_dump($repeat);
for ($curr = 0; $curr < $repeat; $curr++) {
$output->writeln('Hello !');
}
$output->writeln('FIN', ConsoleOutputWithMonolog::OPTION_SKIP_MONOLOG);
return Command::SUCCESS;
}

Loading…
Cancel
Save