mirror of
https://github.com/Respect/Validation.git
synced 2024-06-08 00:32:16 +02:00
Update the validation engine of composite-based rules
This change will also make the composite-based rules require at least two rules in their constructor because those rules do not make sense with only one rule. Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
parent
41245f663f
commit
c04034c2a4
|
@ -1,6 +1,6 @@
|
|||
# AllOf
|
||||
|
||||
- `AllOf(Validatable ...$rule)`
|
||||
- `AllOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)`
|
||||
|
||||
Will validate if all inner validators validates.
|
||||
|
||||
|
@ -17,6 +17,7 @@ v::allOf(v::intVal(), v::positive())->validate(15); // true
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
3.0.0 | Require at least two rules to be passed
|
||||
0.3.9 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# AnyOf
|
||||
|
||||
- `AnyOf(Validatable ...$rule)`
|
||||
- `AnyOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)`
|
||||
|
||||
This is a group validator that acts as an OR operator.
|
||||
|
||||
|
@ -22,6 +22,7 @@ so `AnyOf()` returns true.
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
3.0.0 | Require at least two rules to be passed
|
||||
2.0.0 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# KeySet
|
||||
|
||||
- `KeySet(Key ...$rule)`
|
||||
- `KeySet(Key $rule, Key ...$rules)`
|
||||
|
||||
Validates a keys in a defined structure.
|
||||
|
||||
|
@ -57,6 +57,7 @@ The keys' order is not considered in the validation.
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
3.0.0 | Require at one rule to be passed
|
||||
2.3.0 | KeySet is NonNegatable, fixed message with extra keys
|
||||
1.0.0 | Created
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# NoneOf
|
||||
|
||||
- `NoneOf(Validatable ...$rule)`
|
||||
- `NoneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)`
|
||||
|
||||
Validates if NONE of the given validators validate:
|
||||
|
||||
|
@ -22,6 +22,7 @@ In the sample above, 'foo' isn't a integer nor a float, so noneOf returns true.
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
3.0.0 | Require at least two rules to be passed
|
||||
0.3.9 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# OneOf
|
||||
|
||||
- `OneOf(Validatable ...$rule)`
|
||||
- `OneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)`
|
||||
|
||||
Will validate if exactly one inner validator passes.
|
||||
|
||||
|
@ -23,7 +23,7 @@ character, one or the other, but not neither nor both.
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
2.0.0 | Changed to pass if only one inner validator passes
|
||||
3.0.0 | Require at least two rules to be passed
|
||||
0.3.9 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -10,11 +10,10 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation;
|
||||
|
||||
use finfo;
|
||||
use Respect\Validation\Rules\Key;
|
||||
|
||||
interface ChainedValidator extends Validatable
|
||||
{
|
||||
public function allOf(Validatable ...$rule): ChainedValidator;
|
||||
public function allOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public function alnum(string ...$additionalChars): ChainedValidator;
|
||||
|
||||
|
@ -24,7 +23,7 @@ interface ChainedValidator extends Validatable
|
|||
|
||||
public function alwaysValid(): ChainedValidator;
|
||||
|
||||
public function anyOf(Validatable ...$rule): ChainedValidator;
|
||||
public function anyOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public function arrayType(): ChainedValidator;
|
||||
|
||||
|
@ -66,9 +65,7 @@ interface ChainedValidator extends Validatable
|
|||
|
||||
public function contains(mixed $containsValue, bool $identical = false): ChainedValidator;
|
||||
|
||||
/**
|
||||
* @param mixed[] $needles
|
||||
*/
|
||||
/** @param non-empty-array<mixed> $needles */
|
||||
public function containsAny(array $needles, bool $strictCompareArray = false): ChainedValidator;
|
||||
|
||||
public function countable(): ChainedValidator;
|
||||
|
@ -176,7 +173,7 @@ interface ChainedValidator extends Validatable
|
|||
bool $mandatory = true
|
||||
): ChainedValidator;
|
||||
|
||||
public function keySet(Key ...$rule): ChainedValidator;
|
||||
public function keySet(Validatable $rule, Validatable ...$rules): ChainedValidator;
|
||||
|
||||
public function lazyConsecutive(callable $ruleCreator, callable ...$ruleCreators): ChainedValidator;
|
||||
|
||||
|
@ -219,7 +216,7 @@ interface ChainedValidator extends Validatable
|
|||
|
||||
public function no(bool $useLocale = false): ChainedValidator;
|
||||
|
||||
public function noneOf(Validatable ...$rule): ChainedValidator;
|
||||
public function noneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public function not(Validatable $rule): ChainedValidator;
|
||||
|
||||
|
@ -245,7 +242,7 @@ interface ChainedValidator extends Validatable
|
|||
|
||||
public function odd(): ChainedValidator;
|
||||
|
||||
public function oneOf(Validatable ...$rule): ChainedValidator;
|
||||
public function oneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public function optional(Validatable $rule): ChainedValidator;
|
||||
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
abstract class AbstractComposite extends AbstractRule
|
||||
{
|
||||
/**
|
||||
* @var Validatable[]
|
||||
*/
|
||||
private array $rules = [];
|
||||
|
||||
public function __construct(Validatable ...$rules)
|
||||
{
|
||||
$this->rules = $rules;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$parentName = $this->getName();
|
||||
foreach ($this->rules as $rule) {
|
||||
$ruleName = $rule->getName();
|
||||
if ($ruleName && $parentName !== $ruleName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rule->setName($name);
|
||||
}
|
||||
|
||||
return parent::setName($name);
|
||||
}
|
||||
|
||||
public function addRule(Validatable $rule): self
|
||||
{
|
||||
if ($this->shouldHaveNameOverwritten($rule) && $this->getName() !== null) {
|
||||
$rule->setName($this->getName());
|
||||
}
|
||||
|
||||
$this->rules[] = $rule;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Validatable[]
|
||||
*/
|
||||
public function getRules(): array
|
||||
{
|
||||
return $this->rules;
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
$exceptions = $this->getAllThrownExceptions($input);
|
||||
if (empty($exceptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exception = $this->reportError($input);
|
||||
if ($exception instanceof NestedValidationException) {
|
||||
$exception->addChildren($exceptions);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ValidationException[]
|
||||
*/
|
||||
private function getAllThrownExceptions(mixed $input): array
|
||||
{
|
||||
$exceptions = [];
|
||||
foreach ($this->getRules() as $rule) {
|
||||
try {
|
||||
$rule->assert($input);
|
||||
} catch (ValidationException $exception) {
|
||||
$this->updateExceptionTemplate($exception);
|
||||
$exceptions[] = $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return $exceptions;
|
||||
}
|
||||
|
||||
private function shouldHaveNameOverwritten(Validatable $rule): bool
|
||||
{
|
||||
return $this->hasName($this) && !$this->hasName($rule);
|
||||
}
|
||||
|
||||
private function hasName(Validatable $rule): bool
|
||||
{
|
||||
return $rule->getName() !== null;
|
||||
}
|
||||
|
||||
private function updateExceptionTemplate(ValidationException $exception): void
|
||||
{
|
||||
if ($this->getTemplate() === null || $exception->hasCustomTemplate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exception->updateTemplate($this->getTemplate());
|
||||
|
||||
if (!$exception instanceof NestedValidationException) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($exception->getChildren() as $childException) {
|
||||
$this->updateExceptionTemplate($childException);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Attributes\ExceptionClass;
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Rule;
|
||||
|
@ -20,7 +18,6 @@ use function array_map;
|
|||
use function array_reduce;
|
||||
use function count;
|
||||
|
||||
#[ExceptionClass(NestedValidationException::class)]
|
||||
#[Template(
|
||||
'These rules must pass for {{name}}',
|
||||
'These rules must not pass for {{name}}',
|
||||
|
@ -31,7 +28,7 @@ use function count;
|
|||
'None of these rules must pass for {{name}}',
|
||||
self::TEMPLATE_NONE,
|
||||
)]
|
||||
final class AllOf extends AbstractComposite
|
||||
final class AllOf extends Composite
|
||||
{
|
||||
public const TEMPLATE_NONE = '__none__';
|
||||
public const TEMPLATE_SOME = '__some__';
|
||||
|
@ -48,40 +45,4 @@ final class AllOf extends AbstractComposite
|
|||
|
||||
return (new Result($valid, $input, $this, $template))->withChildren(...$children);
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
try {
|
||||
parent::assert($input);
|
||||
} catch (NestedValidationException $exception) {
|
||||
if (count($exception->getChildren()) === count($this->getRules()) && !$exception->hasCustomTemplate()) {
|
||||
$exception->updateTemplate(self::TEMPLATE_NONE);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
foreach ($this->getRules() as $rule) {
|
||||
$rule->check($input);
|
||||
}
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
foreach ($this->getRules() as $rule) {
|
||||
if (!$rule->validate($input)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getStandardTemplate(mixed $input): string
|
||||
{
|
||||
return self::TEMPLATE_SOME;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,23 +9,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Attributes\ExceptionClass;
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Rule;
|
||||
|
||||
use function array_map;
|
||||
use function array_reduce;
|
||||
use function count;
|
||||
|
||||
#[ExceptionClass(NestedValidationException::class)]
|
||||
#[Template(
|
||||
'At least one of these rules must pass for {{name}}',
|
||||
'At least one of these rules must not pass for {{name}}',
|
||||
)]
|
||||
final class AnyOf extends AbstractComposite
|
||||
final class AnyOf extends Composite
|
||||
{
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
|
@ -34,47 +29,4 @@ final class AnyOf extends AbstractComposite
|
|||
|
||||
return (new Result($valid, $input, $this))->withChildren(...$children);
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
try {
|
||||
parent::assert($input);
|
||||
} catch (NestedValidationException $exception) {
|
||||
if (count($exception->getChildren()) === count($this->getRules())) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
foreach ($this->getRules() as $v) {
|
||||
if ($v->validate($input)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
foreach ($this->getRules() as $v) {
|
||||
try {
|
||||
$v->check($input);
|
||||
|
||||
return;
|
||||
} catch (ValidationException $e) {
|
||||
if (!isset($firstException)) {
|
||||
$firstException = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($firstException)) {
|
||||
throw $firstException;
|
||||
}
|
||||
|
||||
throw $this->reportError($input);
|
||||
}
|
||||
}
|
||||
|
|
70
library/Rules/Composite.php
Normal file
70
library/Rules/Composite.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Helpers\DeprecatedValidatableMethods;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
use function array_merge;
|
||||
|
||||
abstract class Composite implements Validatable
|
||||
{
|
||||
use DeprecatedValidatableMethods;
|
||||
|
||||
/** @var non-empty-array<Validatable> */
|
||||
private readonly array $rules;
|
||||
|
||||
private ?string $name = null;
|
||||
|
||||
private ?string $template = null;
|
||||
|
||||
public function __construct(Validatable $rule1, Validatable $rule2, Validatable ...$rules)
|
||||
{
|
||||
$this->rules = array_merge([$rule1, $rule2], $rules);
|
||||
}
|
||||
|
||||
/** @return non-empty-array<Validatable> */
|
||||
public function getRules(): array
|
||||
{
|
||||
return $this->rules;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
foreach ($this->getRules() as $rule) {
|
||||
if ($rule->getName() && $this->name !== $rule->getName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rule->setName($name);
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setTemplate(string $template): static
|
||||
{
|
||||
$this->template = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTemplate(): ?string
|
||||
{
|
||||
return $this->template;
|
||||
}
|
||||
}
|
|
@ -9,9 +9,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
|
||||
use Respect\Validation\Message\Template;
|
||||
|
||||
use function array_map;
|
||||
use function count;
|
||||
|
||||
#[Template(
|
||||
'{{name}} must contain at least one of the values {{needles}}',
|
||||
|
@ -20,13 +22,19 @@ use function array_map;
|
|||
final class ContainsAny extends Envelope
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $needles At least one of the values provided must be found in input string or array
|
||||
* @param non-empty-array<mixed> $needles At least one of the values provided must be found in input string or array
|
||||
* @param bool $identical Defines whether the value should be compared strictly, when validating array
|
||||
*/
|
||||
public function __construct(array $needles, bool $identical = false)
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
if (empty($needles)) {
|
||||
throw new InvalidRuleConstructorException('At least one value must be provided');
|
||||
}
|
||||
|
||||
$rules = $this->getRules($needles, $identical);
|
||||
parent::__construct(
|
||||
new AnyOf(...$this->getRules($needles, $identical)),
|
||||
count($rules) === 1 ? $rules[0] : new AnyOf(...$rules),
|
||||
['needles' => $needles]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use Respect\Validation\Validatable;
|
|||
use function array_diff;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function count;
|
||||
|
||||
|
@ -42,14 +43,14 @@ final class KeySet extends Wrapper
|
|||
/** @var array<string|int> */
|
||||
private readonly array $keys;
|
||||
|
||||
public function __construct(Validatable ...$rules)
|
||||
public function __construct(Validatable $rule, Validatable ...$rules)
|
||||
{
|
||||
/** @var array<Key> $keyRules */
|
||||
$keyRules = $this->extractMany($rules, Key::class);
|
||||
$keyRules = $this->extractMany(array_merge([$rule], $rules), Key::class);
|
||||
|
||||
$this->keys = array_map(static fn(Key $rule) => $rule->getReference(), $keyRules);
|
||||
$this->keys = array_map(static fn(Key $keyRule) => $keyRule->getReference(), $keyRules);
|
||||
|
||||
parent::__construct(new AllOf(...$keyRules));
|
||||
parent::__construct(count($keyRules) === 1 ? $keyRules[0] : new AllOf(...$keyRules));
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
|
|
|
@ -9,22 +9,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Attributes\ExceptionClass;
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Rule;
|
||||
|
||||
use function array_map;
|
||||
use function array_reduce;
|
||||
use function count;
|
||||
|
||||
#[ExceptionClass(NestedValidationException::class)]
|
||||
#[Template(
|
||||
'None of these rules must pass for {{name}}',
|
||||
'All of these rules must pass for {{name}}',
|
||||
)]
|
||||
final class NoneOf extends AbstractComposite
|
||||
final class NoneOf extends Composite
|
||||
{
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
|
@ -33,26 +29,4 @@ final class NoneOf extends AbstractComposite
|
|||
|
||||
return (new Result($valid, $input, $this))->withChildren(...$children);
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
try {
|
||||
parent::assert($input);
|
||||
} catch (NestedValidationException $exception) {
|
||||
if (count($exception->getChildren()) !== count($this->getRules())) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
foreach ($this->getRules() as $rule) {
|
||||
if ($rule->validate($input)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,24 +9,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Attributes\ExceptionClass;
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Rule;
|
||||
|
||||
use function array_map;
|
||||
use function array_reduce;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
|
||||
#[ExceptionClass(NestedValidationException::class)]
|
||||
#[Template(
|
||||
'Only one of these rules must pass for {{name}}',
|
||||
'Only one of these rules must not pass for {{name}}',
|
||||
)]
|
||||
final class OneOf extends AbstractComposite
|
||||
final class OneOf extends Composite
|
||||
{
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
|
@ -35,50 +29,4 @@ final class OneOf extends AbstractComposite
|
|||
|
||||
return (new Result($count === 1, $input, $this))->withChildren(...$children);
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
try {
|
||||
parent::assert($input);
|
||||
} catch (NestedValidationException $exception) {
|
||||
if (count($exception->getChildren()) !== count($this->getRules()) - 1) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
$rulesPassedCount = 0;
|
||||
foreach ($this->getRules() as $rule) {
|
||||
if (!$rule->validate($input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++$rulesPassedCount;
|
||||
}
|
||||
|
||||
return $rulesPassedCount === 1;
|
||||
}
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
$exceptions = [];
|
||||
$rulesPassedCount = 0;
|
||||
foreach ($this->getRules() as $rule) {
|
||||
try {
|
||||
$rule->check($input);
|
||||
|
||||
++$rulesPassedCount;
|
||||
} catch (ValidationException $exception) {
|
||||
$exceptions[] = $exception;
|
||||
}
|
||||
}
|
||||
|
||||
if ($rulesPassedCount === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw array_shift($exceptions) ?: $this->reportError($input);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use finfo;
|
|||
|
||||
interface StaticValidator
|
||||
{
|
||||
public static function allOf(Validatable ...$rule): ChainedValidator;
|
||||
public static function allOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public static function alnum(string ...$additionalChars): ChainedValidator;
|
||||
|
||||
|
@ -23,7 +23,7 @@ interface StaticValidator
|
|||
|
||||
public static function alwaysValid(): ChainedValidator;
|
||||
|
||||
public static function anyOf(Validatable ...$rule): ChainedValidator;
|
||||
public static function anyOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public static function arrayType(): ChainedValidator;
|
||||
|
||||
|
@ -67,9 +67,7 @@ interface StaticValidator
|
|||
|
||||
public static function contains(mixed $containsValue, bool $identical = false): ChainedValidator;
|
||||
|
||||
/**
|
||||
* @param mixed[] $needles
|
||||
*/
|
||||
/** @param non-empty-array<mixed> $needles */
|
||||
public static function containsAny(array $needles, bool $strictCompareArray = false): ChainedValidator;
|
||||
|
||||
public static function countable(): ChainedValidator;
|
||||
|
@ -177,7 +175,7 @@ interface StaticValidator
|
|||
bool $mandatory = true
|
||||
): ChainedValidator;
|
||||
|
||||
public static function keySet(Validatable ...$rule): ChainedValidator;
|
||||
public static function keySet(Validatable $rule, Validatable ...$rules): ChainedValidator;
|
||||
|
||||
public static function lazyConsecutive(callable $ruleCreator, callable ...$ruleCreators): ChainedValidator;
|
||||
|
||||
|
@ -220,7 +218,7 @@ interface StaticValidator
|
|||
|
||||
public static function no(bool $useLocale = false): ChainedValidator;
|
||||
|
||||
public static function noneOf(Validatable ...$rule): ChainedValidator;
|
||||
public static function noneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public static function not(Validatable $rule): ChainedValidator;
|
||||
|
||||
|
@ -246,7 +244,7 @@ interface StaticValidator
|
|||
|
||||
public static function odd(): ChainedValidator;
|
||||
|
||||
public static function oneOf(Validatable ...$rule): ChainedValidator;
|
||||
public static function oneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator;
|
||||
|
||||
public static function optional(Validatable $rule): ChainedValidator;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'vendor/autoload.php';
|
|||
use Respect\Validation\Validator as v;
|
||||
|
||||
$validator = v::create()
|
||||
->key('age', v::intType()->notEmpty()->noneOf(v::stringType()))
|
||||
->key('age', v::intType()->notEmpty()->noneOf(v::stringType(), v::arrayType()))
|
||||
->key('reference', v::stringType()->notEmpty()->length(1, 50));
|
||||
|
||||
exceptionFullMessage(static fn() => $validator->assert(['age' => 1]));
|
||||
|
|
|
@ -8,11 +8,10 @@ require 'vendor/autoload.php';
|
|||
use Respect\Validation\Validator as v;
|
||||
|
||||
run([
|
||||
'Single rule' => [v::allOf(v::stringType()), 1],
|
||||
'Two rules' => [v::allOf(v::intType(), v::negative()), '2'],
|
||||
'Wrapped by "not"' => [v::not(v::allOf(v::intType(), v::positive())), 3],
|
||||
'Wrapping "not"' => [v::allOf(v::not(v::intType(), v::positive())), 4],
|
||||
'With a single template' => [v::allOf(v::stringType()), 5, 'This is a single template'],
|
||||
'Wrapping "not"' => [v::allOf(v::not(v::intType(), v::positive()), v::greaterThan(2)), 4],
|
||||
'With a single template' => [v::allOf(v::stringType(), v::arrayType()), 5, 'This is a single template'],
|
||||
'With multiple templates' => [
|
||||
v::allOf(v::stringType(), v::uppercase()),
|
||||
5,
|
||||
|
@ -25,14 +24,6 @@ run([
|
|||
]);
|
||||
?>
|
||||
--EXPECT--
|
||||
Single rule
|
||||
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
|
||||
1 must be of type string
|
||||
- 1 must be of type string
|
||||
[
|
||||
'stringType' => '1 must be of type string',
|
||||
]
|
||||
|
||||
Two rules
|
||||
⎺⎺⎺⎺⎺⎺⎺⎺⎺
|
||||
"2" must be of type integer
|
||||
|
|
21
tests/library/Rules/ConcreteComposite.php
Normal file
21
tests/library/Rules/ConcreteComposite.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Test\Rules;
|
||||
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Rules\Composite;
|
||||
|
||||
final class ConcreteComposite extends Composite
|
||||
{
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
return Result::passed($input, $this);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Test\Stubs;
|
||||
|
||||
use Respect\Validation\Message\Parameter\Stringify;
|
||||
use Respect\Validation\Message\TemplateRenderer;
|
||||
use Respect\Validation\Rules\AbstractComposite;
|
||||
use Respect\Validation\Test\Exceptions\CompositeStubException;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
final class CompositeSub extends AbstractComposite
|
||||
{
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $extraParameters
|
||||
*/
|
||||
public function reportError(mixed $input, array $extraParameters = []): CompositeStubException
|
||||
{
|
||||
return new CompositeStubException(
|
||||
input: $input,
|
||||
id: 'CompositeStub',
|
||||
params: $extraParameters,
|
||||
template: Validatable::TEMPLATE_STANDARD,
|
||||
templates: [],
|
||||
formatter: new TemplateRenderer(static fn ($value) => $value, new Stringify())
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\Exceptions\CompositeStubException;
|
||||
use Respect\Validation\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\Stubs\CompositeSub;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
use function current;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(AbstractComposite::class)]
|
||||
final class AbstractCompositeTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itShouldUpdateTheNameOfTheChildWhenUpdatingItsName(): void
|
||||
{
|
||||
$ruleName = 'something';
|
||||
|
||||
$child = Stub::pass(1);
|
||||
|
||||
$parent = new CompositeSub($child);
|
||||
|
||||
self::assertNull($child->getName());
|
||||
|
||||
$parent->setName($ruleName);
|
||||
|
||||
self::assertSame($ruleName, $child->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateTheNameOfTheChildWhenAddingIt(): void
|
||||
{
|
||||
$ruleName = 'something';
|
||||
|
||||
$rule = Stub::pass(1);
|
||||
|
||||
$sut = new CompositeSub();
|
||||
$sut->setName($ruleName);
|
||||
|
||||
self::assertNull($rule->getName());
|
||||
|
||||
$sut->addRule($rule);
|
||||
|
||||
self::assertSame($ruleName, $rule->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldNotUpdateTheNameOfTheChildWhenUpdatingItsNameIfTheChildAlreadyHasSomeName(): void
|
||||
{
|
||||
$ruleName1 = 'something';
|
||||
$ruleName2 = 'something else';
|
||||
|
||||
$rule = Stub::pass(1);
|
||||
$rule->setName($ruleName1);
|
||||
|
||||
$sut = new CompositeSub($rule);
|
||||
$sut->setName($ruleName2);
|
||||
|
||||
self::assertSame($ruleName1, $rule->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itNotShouldUpdateTheNameOfTheChildWhenAddingItIfTheChildAlreadyHasSomeName(): void
|
||||
{
|
||||
$ruleName1 = 'something';
|
||||
$ruleName2 = 'something else';
|
||||
|
||||
$rule = Stub::pass(1);
|
||||
$rule->setName($ruleName1);
|
||||
|
||||
$sut = new CompositeSub();
|
||||
$sut->setName($ruleName2);
|
||||
$sut->addRule($rule);
|
||||
|
||||
self::assertSame($ruleName1, $rule->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldReturnItsChildren(): void
|
||||
{
|
||||
$child1 = Stub::pass(1);
|
||||
$child2 = Stub::pass(1);
|
||||
$child3 = Stub::pass(1);
|
||||
|
||||
$sut = new CompositeSub($child1, $child2, $child3);
|
||||
$children = $sut->getRules();
|
||||
|
||||
self::assertCount(3, $children);
|
||||
self::assertSame($child1, $children[0]);
|
||||
self::assertSame($child2, $children[1]);
|
||||
self::assertSame($child3, $children[2]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldAssertWithAllChildrenAndNotThrowAnExceptionWhenThereAreNoIssues(): void
|
||||
{
|
||||
$input = 'something';
|
||||
|
||||
$child1 = Stub::pass(1);
|
||||
$child2 = Stub::pass(1);
|
||||
$child3 = Stub::pass(1);
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
$sut = new CompositeSub($child1, $child2, $child3);
|
||||
$sut->assert($input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldAssertWithAllChildrenAndThrowAnExceptionWhenThereAreIssues(): void
|
||||
{
|
||||
$sut = new CompositeSub(Stub::fail(1), Stub::fail(1), Stub::fail(1));
|
||||
|
||||
try {
|
||||
$sut->assert('something');
|
||||
} catch (CompositeStubException $exception) {
|
||||
self::assertCount(3, $exception->getChildren());
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateTheTemplateOfEveryChildrenWhenAsserting(): void
|
||||
{
|
||||
$template = 'This is my template';
|
||||
|
||||
$sut = new CompositeSub(
|
||||
Stub::fail(1),
|
||||
Stub::fail(1),
|
||||
Stub::fail(1)
|
||||
);
|
||||
$sut->setTemplate($template);
|
||||
|
||||
try {
|
||||
$sut->assert('something');
|
||||
} catch (CompositeStubException $exception) {
|
||||
foreach ($exception->getChildren() as $child) {
|
||||
self::assertEquals($template, $child->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateTheTemplateOfEveryTheChildrenOfSomeChildWhenAsserting(): void
|
||||
{
|
||||
$template = 'This is my template';
|
||||
|
||||
$sut = new CompositeSub(
|
||||
Stub::fail(1),
|
||||
Stub::fail(1),
|
||||
new CompositeSub(Stub::fail(1))
|
||||
);
|
||||
$sut->setTemplate($template);
|
||||
|
||||
try {
|
||||
$sut->assert('something');
|
||||
} catch (CompositeStubException $exception) {
|
||||
foreach ($exception->getChildren() as $child) {
|
||||
self::assertEquals($template, $child->getMessage());
|
||||
if (!$child instanceof CompositeStubException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::assertNotFalse(current($child->getChildren()));
|
||||
self::assertEquals($template, current($child->getChildren())->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,14 +21,13 @@ final class AllOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{AllOf, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
yield 'pass' => [new AllOf(Stub::pass(1)), []];
|
||||
yield 'pass, pass' => [new AllOf(Stub::pass(1), Stub::pass(1)), []];
|
||||
yield 'pass, pass, pass' => [new AllOf(Stub::pass(1), Stub::pass(1), Stub::pass(1)), []];
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{AllOf, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
yield 'fail' => [new AllOf(Stub::fail(1)), []];
|
||||
yield 'pass, fail' => [new AllOf(Stub::pass(1), Stub::fail(1)), []];
|
||||
yield 'fail, pass' => [new AllOf(Stub::fail(1), Stub::pass(1)), []];
|
||||
yield 'pass, pass, fail' => [new AllOf(Stub::pass(1), Stub::pass(1), Stub::fail(1)), []];
|
||||
|
|
|
@ -21,7 +21,6 @@ final class AnyOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{AnyOf, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
yield 'pass' => [new AnyOf(Stub::pass(1)), []];
|
||||
yield 'fail, pass' => [new AnyOf(Stub::fail(1), Stub::pass(1)), []];
|
||||
yield 'fail, fail, pass' => [new AnyOf(Stub::fail(1), Stub::fail(1), Stub::pass(1)), []];
|
||||
yield 'fail, pass, fail' => [new AnyOf(Stub::fail(1), Stub::pass(1), Stub::fail(1)), []];
|
||||
|
@ -30,7 +29,6 @@ final class AnyOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{AnyOf, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
yield 'fail' => [new AnyOf(Stub::fail(1)), []];
|
||||
yield 'fail, fail' => [new AnyOf(Stub::fail(1), Stub::fail(1)), []];
|
||||
yield 'fail, fail, fail' => [new AnyOf(Stub::fail(1), Stub::fail(1), Stub::fail(1)), []];
|
||||
}
|
||||
|
|
84
tests/unit/Rules/CompositeTest.php
Normal file
84
tests/unit/Rules/CompositeTest.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\Rules\ConcreteComposite;
|
||||
use Respect\Validation\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('core')]
|
||||
#[CoversClass(Composite::class)]
|
||||
final class CompositeTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itShouldReturnItsChildren(): void
|
||||
{
|
||||
$expected = [Stub::daze(), Stub::daze(), Stub::daze()];
|
||||
$sut = new ConcreteComposite(...$expected);
|
||||
$actual = $sut->getRules();
|
||||
|
||||
self::assertCount(3, $actual);
|
||||
self::assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldDefineAndRetrieveTemplate(): void
|
||||
{
|
||||
$template = 'This is a template';
|
||||
|
||||
$sut = new ConcreteComposite(Stub::daze(), Stub::daze());
|
||||
$sut->setTemplate($template);
|
||||
|
||||
self::assertEquals($template, $sut->getTemplate());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldUpdateTheNameOfTheChildWhenUpdatingItsName(): void
|
||||
{
|
||||
$ruleName = 'something';
|
||||
|
||||
$rule1 = Stub::daze();
|
||||
$rule2 = Stub::daze();
|
||||
|
||||
$composite = new ConcreteComposite($rule1, $rule2);
|
||||
|
||||
self::assertNull($rule1->getName());
|
||||
self::assertNull($rule2->getName());
|
||||
|
||||
$composite->setName($ruleName);
|
||||
|
||||
self::assertEquals($ruleName, $rule1->getName());
|
||||
self::assertEquals($ruleName, $rule2->getName());
|
||||
self::assertEquals($ruleName, $composite->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itShouldNotUpdateTheNameOfTheChildWhenUpdatingItsNameIfTheChildAlreadyHasSomeName(): void
|
||||
{
|
||||
$ruleName1 = 'something';
|
||||
$ruleName2 = 'something else';
|
||||
|
||||
$rule1 = Stub::daze();
|
||||
$rule1->setName($ruleName1);
|
||||
|
||||
$rule2 = Stub::daze();
|
||||
$rule2->setName($ruleName1);
|
||||
|
||||
$composite = new ConcreteComposite($rule1, $rule2);
|
||||
$composite->setName($ruleName2);
|
||||
|
||||
self::assertEquals($ruleName1, $rule1->getName());
|
||||
self::assertEquals($ruleName1, $rule2->getName());
|
||||
self::assertEquals($ruleName2, $composite->getName());
|
||||
}
|
||||
}
|
|
@ -11,12 +11,24 @@ namespace Respect\Validation\Rules;
|
|||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(ContainsAny::class)]
|
||||
final class ContainsAnyTest extends RuleTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itShouldThrowAnExceptionWhenThereAreNoNeedles(): void
|
||||
{
|
||||
$this->expectException(InvalidRuleConstructorException::class);
|
||||
$this->expectExceptionMessage('At least one value must be provided');
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
new ContainsAny([]);
|
||||
}
|
||||
|
||||
/** @return iterable<array{ContainsAny, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
|
|
|
@ -21,7 +21,6 @@ final class NoneOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{NoneOf, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
yield 'fail' => [new NoneOf(Stub::fail(1)), []];
|
||||
yield 'fail, fail' => [new NoneOf(Stub::fail(1), Stub::fail(1)), []];
|
||||
yield 'fail, fail, fail' => [new NoneOf(Stub::fail(1), Stub::fail(1), Stub::fail(1)), []];
|
||||
}
|
||||
|
@ -29,7 +28,6 @@ final class NoneOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{NoneOf, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
yield 'pass' => [new NoneOf(Stub::pass(1)), []];
|
||||
yield 'pass, fail' => [new NoneOf(Stub::pass(1), Stub::fail(1)), []];
|
||||
yield 'fail, pass' => [new NoneOf(Stub::fail(1), Stub::pass(1)), []];
|
||||
yield 'pass, pass, fail' => [new NoneOf(Stub::pass(1), Stub::pass(1), Stub::fail(1)), []];
|
||||
|
|
|
@ -21,7 +21,6 @@ final class OneOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{OneOf, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
yield 'pass' => [new OneOf(Stub::pass(1)), []];
|
||||
yield 'fail, pass' => [new OneOf(Stub::fail(1), Stub::pass(1)), []];
|
||||
yield 'pass, fail' => [new OneOf(Stub::pass(1), Stub::fail(1)), []];
|
||||
yield 'pass, fail, fail' => [new OneOf(Stub::pass(1), Stub::fail(1), Stub::fail(1)), []];
|
||||
|
@ -32,7 +31,6 @@ final class OneOfTest extends RuleTestCase
|
|||
/** @return iterable<string, array{OneOf, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
yield 'fail' => [new OneOf(Stub::fail(1)), []];
|
||||
yield 'fail, fail' => [new OneOf(Stub::fail(1), Stub::fail(1)), []];
|
||||
yield 'fail, fail, fail' => [new OneOf(Stub::fail(1), Stub::fail(1), Stub::fail(1)), []];
|
||||
yield 'fail, pass, pass' => [new OneOf(Stub::fail(1), Stub::pass(1), Stub::pass(1)), []];
|
||||
|
|
Loading…
Reference in a new issue